О том где лежат аргументы можно узнать двумя путями 1 - прочитать и поверить, 2 - посмотреть и проверить.(Вы думаете вызов wait существует в той форме в которой описан?) Что бы узнать где лежат argv и envp нужно сделать execute program с ними, а для этого сделать syscall execve. В man 2 syscall даны способы для различных архитектур. В man 2 execve сказано что argv и envp передаются в main. Пользуясь примерами из man 2 execve, cоздадим две программы - первую для запуска второй, и третью что бы узнать как передаются аргументы в функцию. ./execve.c Code: #include <unistd.h> #include <sys/user.h> int main() { char *filename = "./main"; char *argv[] = {"argument", NULL}; char *envp[] = {"evironment", NULL}; execve(filename, argv, envp); return(-1); } gcc -o ./execve ./execve.c ./main.c Code: int main(int argc, char *argv, char *envp) { return(0); } gcc -o main main.c ./funcargs.c Code: void test(int arg0, int arg1, int arg2, int arg3, int arg4, int arg5, int arg6, int arg7) { } int main() { test(0, 1, 2, 3, 4, 5, 6, 7); return(0); } gcc -o funcargs funcargs.c Перед отладкой добавим в ~/.gdbinit включатель дезасэмблирования в синтаксе intel Code: set disassembly-flavor intel Отладка. gdb ./execve break main run Добавим остановку после системного вызова. catch syscall continue disas Запоминаем адрес syscall (0x00007ffff7af2645) Прерываем исполнение, удаляем catch, запускаемся. signal 9 info break delete 2 run Останавливаемся на main, ставим бряк на адрес syscall. break *0x00007ffff7af2645 continue info reg В rax номер execve 0x3b Рассмотрим аргументы из rsi и rdx. x/xg 0x7fffffffdff0 x/xg 0x7fffffffdfe0 Мы передавали массив указателей значит там лежат указатели на строки. x/s 0x000055555555479b x/s 0x00005555555547a4 А если увеличить адреса на 8 то там будут лежать нули. x/xg 0x7fffffffdff8 x/xg 0x7fffffffdfe8 Необходимые данные создания запускателя получены. Изготовим execve на ассемблере. ./execve.s Code: .global _start .data filename: .asciz "./main" argp: .fill 2, 8, 0 arg0: .asciz "argument" envp: .fill 2, 8, 0 env0: .asciz "environment=value" .text .intel_syntax noprefix _start: mov rax,0x3b lea rdi,filename lea rsi,argp lea rbx,arg0 mov [rsi],rbx lea rdx,envp lea rbx,env0 mov [rdx],rbx syscall _error: mov rax,0x3c mov rdi,-1 syscall Закодировали, склеили, исполнили, отобразили значение выхода. as -o execve.o execve.s && ld -o execve execve.o && ./execve ; echo $? gdb execve b _start r disas Брякнем syscall b *_start+53 c Дошагаем до начала новой программы. si si si break main continue continue Пришло время посмотреть как передаются параметры функциям. Открываем новый терминал. gdb funcargs disas main Порядок передачи такой rdi, rsi, rdx, rcx, r8, r9 остальные через стэк. Закрываем новый терминал, возвращаемся к обратно к argv и envp. i r rdx 0x7fffffffee78 140737488350840 rsi 0x7fffffffee68 140737488350824 rsp 0x7fffffffed80 0x7fffffffed80 Массивы указателей лежат в стэке. Проверим лежат ли они там на старте программы. signal 9 quit gdb execve b _start r disas b *_start+53 c si si si i r rsp 0x7fffffffee60 x/xg 0x7fffffffee68 x/s 0x00007fffffffefd6 0x7fffffffefd6: "argument" Вот и нашли где лежат argv и envp, а rsp указывает на argc. Теперь смастерим пускатель который стартует прогу из argv1 и остальные argv передаёт этой проге. ./execve.s Code: .global _start .data filename: .quad 0x00 argp: .quad 0x00 envp: .fill 1, 8, 0 .text .intel_syntax noprefix _start: mov rbx,[rsp+0x10] mov filename,rbx mov rbx,rsp add rbx,0x10 mov argp,rbx mov rax,0x3b mov rdi,filename mov rsi,argp lea rdx,envp syscall _error: mov rax,0x3c mov rdi,-1 syscall as -o execve.o execve.s && ld -o execve execve.o ./execve /bin/echo "XA!" Этот код пригодится потом для эксперементов с ptrace. Теперь немного о номерах системных вызовов и их ошибках. apt-get download linux-headers-4.9.0-6-amd64 dpkg -x ./linux-headers-4.9.0-6-amd64_4.9.88-1+deb9u1_amd64.deb ./ less ./usr/src/linux-headers-4.9.0-6-amd64/arch/x86/include/generated/asm/syscalls_64.h less /usr/include/asm-generic/errno-base.h less /usr/include/asm-generic/errno.h Перед тем как смотреть ошибку после системного вызова сделайте neg rax.
в RTL они лежат, и линкер вставляет код, который вызывает твою main/WinMain/DllMain. с ртл много приколов, можешь например попробовать такое(в случае с визуал сятиной): (с линупсом я не ебал мозги - ибо бессмысленно) // 1.cpp void DynamicInitializer() { __debugbreak(); } #pragma data_seg(".CRT$XCU") // dynamic initializers list void *pCallbackCTOR = DynamicInitializer; // add pointer for fake initializer #pragma data_seg() поясню за код: в секции объектного кода с тегом .CRT$XCU находится таблица поинтеров на динамические инициализаторы, это в случае MSVC, и до вызова твоей main инициализируется сама библиотека, которая много всякой шляпы по работе с памятью да и с исключениями устанавливает, и в том числе и конструкторы статических объектов вызывает. указанным выше макаром мы вносим еще один указатель в эту секцию, поэтому в результате DynamicInitializer() сработает до вызова main()
Linux же. Какой ушь там DllMain На точке входа esp вроде-как указывает на argc и argv[0] где-то там же рядом. Надо бы затестить но лениво.
да я в курсе, это просто пример. а вот разработчикам нативного кода стоило бы разобраться кто как и что вызывает, немного нырнуть в объектный код - ведь православную иду слили в паблик и не раз, и читать всё это она прекрасно умеет. и если вкратце - то линкер ставит точку входа в рантайм библю, тотже объектный модуль, который по отработке ссылается на символ main WinMain DllMain итд. и всё, нет тут никакой магии, просто проще посмотрить исходный код библиотеки врем ени исполнения. на вход линкеру идут объектные модули, которые закомпилил компилер, и он увязывает все связи, когда например у тебя есть extern int g_NumInstance и обращение к ней в твоём коде - компилер скомпилет это, не проблема, а вот линкер на своём шаге будет уже искать в каком другом модуле её определение, вставлять адреса обращений итд