2013-04-18 2 views
5

Я хочу узнать о конвенции C вызова. Для этого я написал следующий код:Понимание C разобранным звонком

#include <stdio.h> 
#include <stdlib.h> 

struct tstStruct 
{ 
    void *sp; 
    int k; 
}; 

void my_func(struct tstStruct*); 

typedef struct tstStruct strc; 

int main() 
{ 
    char a; 
    a = 'b'; 
    strc* t1 = (strc*) malloc(sizeof(strc)); 
    t1 -> sp = &a; 
    t1 -> k = 40; 
    my_func(t1); 
    return 0; 
} 

void my_func(strc* s1) 
{ 
     void* n = s1 -> sp + 121; 
     int d = s1 -> k + 323; 
} 

Затем я использовал GCC с помощью следующей команды:

gcc -S test3.c 

и придумал его сборки. Я не буду показывать весь код, который я получил, но скорее вставлю код для функции my_func. Именно это:

my_func: 
.LFB1: 
.cfi_startproc 
pushq %rbp 
.cfi_def_cfa_offset 16 
.cfi_offset 6, -16 
movq %rsp, %rbp 
.cfi_def_cfa_register 6 
movq %rdi, -24(%rbp) 
movq -24(%rbp), %rax 
movq (%rax), %rax 
addq $121, %rax 
movq %rax, -16(%rbp) 
movq -24(%rbp), %rax 
movl 8(%rax), %eax 
addl $323, %eax 
movl %eax, -4(%rbp) 
popq %rbp 
.cfi_def_cfa 7, 8 
ret 
.cfi_endproc 

Насколько я понял, это то, что происходит: Сначала звонящие базовый указатель помещается в стек и его указатель стека сделан новый указатель базы для настройки стеки для нового функция. Но тогда все остальное я не понимаю. Насколько мне известно, аргументы (или указатель на аргумент) хранятся в стеке. Если это так, какова цель второй команды,

movq  -24(%rbp), %rax 

Здесь содержание регистра% RAx перемещается по адресу 24 байт от адреса в регистре% РСП. Но что в% rax ???? Ничего изначально не хранится там? Кажется, я в замешательстве. Пожалуйста, помогите понять, как работает эта функция. Спасибо заранее!

+2

Compile с 'Gcc -fverbose-ASM -S' и, возможно, даже' НКУ -fverbose-ASM -O -S'; см. также [этот ответ] (http://stackoverflow.com/a/16088155/841108), который дает * много * ссылок. –

+0

Спасибо за все ссылки и подсказку компиляции. – user2290802

ответ

9

Вы путаете AT & T синтаксис с синтаксисом Intel.

 
movq -24(%rbp), %rax 

В Intel синтаксис это будет

 
mov rax,[rbp-24] 

Так перемещает данные, решаемые rbp в rax, а не наоборот. Порядок операндов - src, dest в AT & T синтаксис, тогда как в синтаксисе Intel это dest, src.

Затем, чтобы избавиться от директив GAS, чтобы облегчить чтение, я собрал код с gcc просто с gcc test3.c и разобрал его с ndisasm -b 64 a.out. Обратите внимание, что демонтаж my_func функции производимого NDISASM ниже в синтаксисе Intel:

 
000005EF 55    push rbp 
000005F0 4889E5   mov rbp,rsp  ; create the stack frame. 
000005F3 48897DE8   mov [rbp-0x18],rdi ; s1 into a local variable. 
000005F7 488B45E8   mov rax,[rbp-0x18] ; rax = s1 (it's a pointer) 
000005FB 488B00   mov rax,[rax]  ; dereference rax, store into rax. 
000005FE 4883C079   add rax,byte +0x79 ; rax = rax + 121 
00000602 488945F8   mov [rbp-0x8],rax ; void* n = s1 -> sp + 121 
00000606 488B45E8   mov rax,[rbp-0x18] ; rax = pointer to s1 
0000060A 8B4008   mov eax,[rax+0x8] ; dereference rax+8, store into eax. 
0000060D 0543010000  add eax,0x143  ; eax = eax + 323 
00000612 8945F4   mov [rbp-0xc],eax ; int d = s1 -> k + 323 
00000615 5D    pop rbp 
00000616 C3    ret 

Для получения информации о Linux x86-64 соглашение о вызовах (System V ABI), см ответы на What are the calling conventions for UNIX & Linux system calls on x86-64.

+0

Ну, тогда в чем смысл команды movq% rdi, -24 (% rbp)? Что находится в регистре% rdi? – user2290802

+0

@ user2290802 '% rdi' - это первый аргумент, в данном случае' strc * s1'. См. Мой отредактированный ответ для объяснения разборки. – nrz

+2

Ну, если вы хотите создать asm в формате Intel, вы можете использовать 'gcc -masm = intel -S'. Этого должно быть достаточно. – perror

6

Функция раскладывается как это (я игнорировать ненужные строки):

Во-первых, экономия предыдущего фрейма стека:

pushq %rbp 
movq %rsp, %rbp 

Здесь старый %rbp надевается на стек должен храниться до конца функции. Затем значение %rbp установлено на значение нового %rsp (это одна строка ниже сохраненного %rbp при возникновении push).

movq %rdi, -24(%rbp) 

Здесь вы сначала должны знать один из основных различия между i386 system V ABI и amd64 system V ABI.

В i386 System V ABI аргументы функции передаются через стек (и только через стек). Напротив, в amd64 System V ABI, аргументы сначала пропускают через регистры (%rdi, %rsi, %rdx, %rcx, %r8 и %r9 если это целые числа, и %xmm0 к %xmm7 если это поплавки). После того, как количество регистров было исчерпано, остальные аргументы будут перенесены в стек, как в i386.

Итак, здесь машина просто загружает первый аргумент функции (которая является целочисленной) временной в стек.

movq -24(%rbp), %rax 

Поскольку вы не можете передавать данные непосредственно из одного регистра в другой, содержание %rdi затем загружают в %rax. Итак, %rax теперь сохранит первый (и единственный) аргумент этой функции.

movq (%rax), %rax 

Эта команда просто разыменования указателя и сохранения результата обратно в %rax.

addq $121, %rax 

Мы добавили 121 к указанному значению.

movq %rax, -16(%rbp) 

Мы храним полученное значение в стеке.

movq -24(%rbp), %rax 

Мы загружаем, снова первый аргумент функции в %rax (помните, что мы сохранили первый аргумент в -24(%rbp)).

movl 8(%rax), %eax 
addl $323, %eax 

Как и ранее, мы разыменования указателя и сохранить полученное значение в %eax, а затем добавить к нему 323 и положить его обратно в %eax.

Обратите внимание, здесь, что мы перешли от %rax к %eax, поскольку значение, которое мы регулируем не больше а void* (64бит), как ранее но int (32бит).

movl %eax, -4(%rbp) 

Наконец, мы храним результат этого вычисления в стек (который, кажется, бесполезно здесь, но это, вероятно, что-то ненужное, что компилятор не обнаружил во время компиляции).

popq %rbp 
ret 

Два последних инструкции просто восстановить предыдущий кадр стека, прежде чем дать руку к функции main.

Надеюсь, теперь это станет более ясным.

+0

Спасибо, это выглядит ясно. Поэтому я нашел источник своей путаницы. Это 32-битная и 64-разрядная проблема, которая у меня есть. – user2290802

+0

@perror Небольшое пояснение: «Здесь старый% rbp помещается в стек, который должен храниться до конца функции. Затем% rsp устанавливается на значение нового% rbp (это одна строка ниже сохраненный% rbp в результате нажатия). " -> Я думаю, вы смешали rsp и rbp. «movq% rsp,% rbp» устанавливает RBP на значение RSP, а не наоборот ... (AT & T Syntax) – libjup

+0

Вы правы, я имел в виду: «Тогда значение'% rbp' установлено в значение new '% rsp' ..." (я обменялся rsp и rbp). Я исправил это в тексте. Спасибо, что заметили это. – perror

1

Вы можете изменить синтаксис интел, введя следующую команду:

$ gcc -S -masm=intel test3.c -o test3.s 
+0

Привет. Добро пожаловать в StackOverflow. Пожалуйста, ознакомьтесь с тем, как правильно ответить на вопрос :). Обратите внимание, что ваш ответ - не ответ, а скорее намек и поэтому должен быть помещен в комментарии. Приветствия :) – DawidPi

Смежные вопросы