Здравствуйте. Есть исходник
void some_func()
{
SOME_CODE_HERE;
}
int main(int argc, char *argv[])
{
some_func(1,2,3);
return 0;
}
Вопросы:
Как из функции some_func получить аргументы без ассемблерных вставок?
Зачем вообще в Си было ввведено различие между объявлением
-
void some_func() {}
и
void some_func(void) {}
P.S.:
На всякий пожарный: крики "твоё творение не компилится" не принимаются, потому что это враки и gcc всё компилит даже без варнингов.
Заранее спасибо за ответы.
Ответ
Для 32-bit такой фокус возможен, т.к. компилятор передает параметры через стек. В нем же запоминается base pointer (регистр от которого берутся смещения локальных переменных и параметров). Поэтому, если вспомогательная функция вернет значение bp интересующей нас функции, то мы имеем доступ к аргументам.
Короче
int **getbp (int arg)
{
/* caller bp (arg[-2] == bp; arg[-1] == return_addr; arg[0] == arg) */
return (int **)(&arg-2);
}
void some_func()
{
//SOME_CODE_HERE;
write(1,"xaxa
",5);
int **bp = getbp(1);
int *args = *bp + 2;
printf ("%d %d %d
",args[0],args[1],args[2]);
}
int main(int argc, char *argv[])
{
some_func(1, 2, 3);
return 0;
}
Использование:
c:/Users/avp/src/cc/hashcode $ gcc noargs.c
c:/Users/avp/src/cc/hashcode $ ./a
xaxa
1 2 3
c:/Users/avp/src/cc/hashcode $
В 64-bit х86 gcc такая штука не пройдет, так как несколько параметров (в данном случае все) передаются через регистры.
Вы можете посмотреть ассемблерный код (в файле noargs.s) командой:
gcc -S noargs.c
@Котик_хочет_кушать видимо ответил вам (я не вчитывался в аргументацию).
Update
По крайней мере в gcc 5.4.0 (проверял в Ubuntu 16.04.1 LTS) в x86_64 GNU/Linux параметры передаются в функцию в регистрах.
Первые 6 целых (включая указатели) в регистрах rdi, rsi, rdx, rcx, r8 и r9, а первые 8 действительныx (float и double) в регистрах с xmm0 по xmm7. Остальные помещаются в стек в обратном порядке. Т.е. параметры, которые находились в списке вызова функции ближе к его началу, окажутся ближе к вершине стека (в памяти с меньшими адресами, поскольку стек растет от старших адресов к младшим).
Идея извлечения значений параметров без использования ассемблера состоит в том, что надо "обмануть" компилятор и вызвать без аргументов функцию у которой определен подходящий список аргументов, которые она должна положить в память и вернуть эту память в some_func(). Проблема в том, что вызов (и возврат результата) надо проделать так, чтобы компилятор эти регистры (он же, компилируя some_func() считает, что у нее нет параметров) не попортил.
Что касается параметров, которые не поместились в регистры и записаны в стек, то мне удалось достать их только в том случае, когда все функции компилируются без оптимизации.
В таком случае gcc адресует каждый фрейм функции (ее локальные переменные) регистром rbp (при входе в функцию он запоминается в стеке и загружается новым значением указателя стека (rsp)) и связывает их в список.
(самому жутко интересно, каким образом отладчик находит фреймы для программы с оптимизацией?
Кое-что можно прочесть тут, возможно основное там --
gdb has a whole bunch of unwinders, including one that scans the
machine code to figure out how the frame was setup.
что явно выносит попытки написания корректного примера для оптимизированного кода за рамки этого вопроса)
Итак, имеем такую программу (main и some_func)
#include
void some_func()
{
GETARGS64(arg);
puts("some_func integer:");
for (int i = 0; i < N_IREGS || (puts(""), 0); i++)
printf("%ld ", arg.ir[i]);
puts("some_func float:");
for (int i = 0; i < N_FREGS || (puts(""), 0); i++)
printf("%f ", arg.fr[i]);
if (arg.sa.isa)
printf("args in stack: [%ld %ld %ld %lf %lf %lf %ld %ld]
",
arg.sa.isa[0], arg.sa.isa[1], arg.sa.isa[2],
arg.sa.fsa[3], arg.sa.fsa[4], arg.sa.fsa[5],
arg.sa.isa[6], arg.sa.isa[7]);
else
puts("can't get args in stack");
//SOME_CODE_HERE;
}
int
main (int ac, char *av[])
{
some_func(1, 2, 3, 11.1, 12.1, 13.1, 4, 5, 6, 14.1, 15.1, 16.1, 17.1, 18.1,
7, 8, 9, 17.2, 18.2, 19.2, 7001, 7002, 7001.7, 7002.8);
puts("again X 10");
some_func(110.1, 120.1, 130.1, 10, 20, 30, 40, 50, 60,
140.1, 150.1, 160.1, 170.1, 180.1,
70, 80, 90, 170.2, 180.2, 190.2);
return puts("End") == EOF;
}
Для реализации нашего плана пишем вот такой заголовочный файл (getargs.h)
#ifndef _GETARGS64_H
#define _GETARGS64_H
#include
#define N_IREGS 6
#define N_FREGS 8
uint64_t *get_iregs();
double *get_fregs();
uint64_t *get_stackargs (uint64_t a, ...);
struct regargs {
uint64_t ir[N_IREGS];
double fr[N_FREGS];
union {
uint64_t *isa;
double *fsa;
} sa;
};
// макрос определен gcc, если он вызван с ключами оптимизации
#ifndef __OPTIMIZE__
#define STACKARGS(name) name.sa.isa = get_stackargs(1, 2, 3, 4, 5, 6, \
get_stackargs)
#else
#define STACKARGS(name) name.sa.isa = 0
#endif
#define GETARGS64(name) struct regargs name; \
{ \
uint64_t *iargs = get_iregs(); \
double *fargs = get_fregs(); \
\
for (int i = 0; i < N_IREGS; i++) \
name.ir[i] = iargs[i]; \
for (int i = 0; i < N_FREGS; i++) \
name.fr[i] = fargs[i]; \
free(iargs); free(fargs); \
STACKARGS(name); \
}
#endif
с макросом GETARGS64(name), который надо разместить в первой строке some_func(). Этот макрос создает структуру struct regargs с именем name, которая будет заполнена значениями регистров и (если возможно) указателем на оставшиеся параметры some_func в стеке.
И наконец, сами функции, извлекающие значения регистров и параметры вызова в стеке:
#include
#define N_IREGS 6
#define N_FREGS 8
uint64_t *
get_iregs (uint64_t a0, uint64_t a1, uint64_t a2,
uint64_t a3, uint64_t a4, uint64_t a5)
{
uint64_t r[N_IREGS] = {a0, a1, a2, a3, a4, a5};
return memcpy(malloc(N_IREGS * sizeof(uint64_t)),
r, N_IREGS * sizeof(uint64_t));
}
double *
get_fregs (double a0, double a1, double a2, double a3,
double a4, double a5, double a6, double a7)
{
double *fr = malloc(N_FREGS * sizeof(double));
fr[0] = a0; fr[1] = a1; fr[2] = a2; fr[3] = a3;
fr[4] = a4; fr[5] = a5; fr[6] = a6; fr[7] = a7;
return fr;
}
#ifndef __OPTIMIZE__
#define ARGS_OFFSET 2
#include
// check memory access by addr, returns 1 if addr readable/writable, 0 otherwise
static int
is_mem_accessible (void *addr, int writable)
{
if (addr == 0 || addr == (void *)-1LL)
return 0;
volatile int rc = 1;
sigjmp_buf jmp;
// signal handler nested function
void sighdr (int sig) { siglongjmp(jmp, 0); }
void
(* sigbus)(int) = signal(SIGBUS, sighdr),
(* sigsegv)(int) = signal(SIGSEGV, sighdr);
if (sigsetjmp(jmp, 1)) { // MANDATORY save sigmask
rc = 0;
errno = EFAULT;
} else {
volatile char t = *((char *)addr);
if (writable)
*((char *)addr) = t;
}
signal(SIGBUS, sigbus);
signal(SIGSEGV, sigsegv);
return rc;
}
uint64_t *
get_stackargs (uint64_t a, ...)
{
uint64_t *p;
for (p = (uint64_t *)(&p + 1); *p != (uint64_t)get_stackargs; p++);
p = (uint64_t *)p[-2];
return is_mem_accessible (p + ARGS_OFFSET, 1) ?
p + ARGS_OFFSET : 0;
}
#else
uint64_t *
get_stackargs (uint64_t a, ...)
{
return 0;
}
#endif
Если есть какие-то вопросы, задавайте в комментариях (или чате C/C++), попробуем разобраться.
Комментариев нет:
Отправить комментарий