Страницы

Поиск по вопросам

суббота, 7 декабря 2019 г.

Как получить аргументы функции?

#c


Здравствуйте. Есть исходник
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 всё компилит даже без варнингов.
Заранее спасибо за ответы.    


Ответы

Ответ 1



Для 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\n",5); int **bp = getbp(1); int *args = *bp + 2; printf ("%d %d %d\n",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 #include "getargs64.h" 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]\n", 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 #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 #include #include #include #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 #include #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++), попробуем разобраться.

Комментариев нет:

Отправить комментарий