blob: b2d28bcb048b8ddc4d942dc557c4f8960f3eb2ab [file] [edit]
/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*-
*
* This file is part of PRoot.
*
* Copyright (C) 2014 STMicroelectronics
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA.
*/
#include <talloc.h> /* talloc*, */
#include <sys/queue.h> /* STAILQ_*, */
#include <errno.h> /* E*, */
#include <assert.h> /* assert(3), */
#include "syscall/chain.h"
#include "syscall/sysnum.h"
#include "tracee/tracee.h"
#include "tracee/reg.h"
#include "arch.h"
struct chained_syscall {
Sysnum sysnum;
word_t sysargs[6];
STAILQ_ENTRY(chained_syscall) link;
};
STAILQ_HEAD(chained_syscalls, chained_syscall);
/**
* Append a new syscall (@sysnum, @sysarg_*) to the list of
* "unrequested" syscalls for the given @tracee. These new syscalls
* will be triggered in order once the current syscall is done. The
* caller is free to force the last result of this syscall chain in
* @tracee->chain.final_result. This function returns -errno if an
* error occurred, otherwise 0.
*/
int register_chained_syscall(Tracee *tracee, Sysnum sysnum,
word_t sysarg_1, word_t sysarg_2, word_t sysarg_3,
word_t sysarg_4, word_t sysarg_5, word_t sysarg_6)
{
struct chained_syscall *syscall;
if (tracee->chain.syscalls == NULL) {
tracee->chain.syscalls = talloc_zero(tracee, struct chained_syscalls);
if (tracee->chain.syscalls == NULL)
return -ENOMEM;
STAILQ_INIT(tracee->chain.syscalls);
}
syscall = talloc_zero(tracee->chain.syscalls, struct chained_syscall);
if (syscall == NULL)
return -ENOMEM;
syscall->sysnum = sysnum;
syscall->sysargs[0] = sysarg_1;
syscall->sysargs[1] = sysarg_2;
syscall->sysargs[2] = sysarg_3;
syscall->sysargs[3] = sysarg_4;
syscall->sysargs[4] = sysarg_5;
syscall->sysargs[5] = sysarg_6;
STAILQ_INSERT_TAIL(tracee->chain.syscalls, syscall, link);
return 0;
}
/**
* Use/remove the first element of @tracee->chain.syscalls to forge a
* new syscall. This function should be called only at the end of in
* the sysexit stage.
*/
void chain_next_syscall(Tracee *tracee)
{
struct chained_syscall *syscall;
word_t instr_pointer;
word_t sysnum;
assert(tracee->chain.syscalls != NULL);
/* No more chained syscalls: force the result of the initial
* syscall (the one explicitly requested by the tracee). */
if (STAILQ_EMPTY(tracee->chain.syscalls)) {
TALLOC_FREE(tracee->chain.syscalls);
if (tracee->chain.force_final_result)
poke_reg(tracee, SYSARG_RESULT, tracee->chain.final_result);
tracee->chain.force_final_result = false;
tracee->chain.final_result = 0;
return;
}
/* Original register values will be restored right after the
* last chained syscall. */
tracee->restore_original_regs = false;
/* The list of chained syscalls is a FIFO. */
syscall = STAILQ_FIRST(tracee->chain.syscalls);
STAILQ_REMOVE_HEAD(tracee->chain.syscalls, link);
poke_reg(tracee, SYSARG_1, syscall->sysargs[0]);
poke_reg(tracee, SYSARG_2, syscall->sysargs[1]);
poke_reg(tracee, SYSARG_3, syscall->sysargs[2]);
poke_reg(tracee, SYSARG_4, syscall->sysargs[3]);
poke_reg(tracee, SYSARG_5, syscall->sysargs[4]);
poke_reg(tracee, SYSARG_6, syscall->sysargs[5]);
sysnum = detranslate_sysnum(get_abi(tracee), syscall->sysnum);
poke_reg(tracee, SYSTRAP_NUM, sysnum);
/* Move the instruction pointer back to the original trap. */
instr_pointer = peek_reg(tracee, CURRENT, INSTR_POINTER);
poke_reg(tracee, INSTR_POINTER, instr_pointer - SYSTRAP_SIZE);
}
/**
* Force the last result of the @tracee's current syscall chain to be
* @forced_result.
*/
void force_chain_final_result(Tracee *tracee, word_t forced_result)
{
tracee->chain.force_final_result = true;
tracee->chain.final_result = forced_result;
}
/**
* Restart the original syscall of the given @tracee. The result of
* the current syscall will be overwritten. This function returns the
* same status as register_chained_syscall().
*/
int restart_original_syscall(Tracee *tracee)
{
return register_chained_syscall(tracee,
get_sysnum(tracee, ORIGINAL),
peek_reg(tracee, ORIGINAL, SYSARG_1),
peek_reg(tracee, ORIGINAL, SYSARG_2),
peek_reg(tracee, ORIGINAL, SYSARG_3),
peek_reg(tracee, ORIGINAL, SYSARG_4),
peek_reg(tracee, ORIGINAL, SYSARG_5),
peek_reg(tracee, ORIGINAL, SYSARG_6));
}