Защо C не натиска указател върху стека при извикване на функция за асемблиране?

В момента се опитвам да придобия опит с извикването на асемблиращи функции от C. Затова създадох малка програма, която изчислява сумата на всички елементи на масива.

C кодът изглежда така:

#include <stdio.h>
#include <stdint.h>

extern int32_t arrsum(int32_t* arr,int32_t length);

int main()
{
    int32_t test[] = {1,2,3};
    int32_t length = 3;
    int32_t sum = arrsum(test,length);
    printf("Sum of arr: %d\n",sum);
    return 0;
}

И функцията за сглобяване изглежда така:

.text
.global arrsum
arrsum:

    pushq %rbp
    movq %rsp, %rbp

    pushq %rdi
    pushq %rcx

    movq 24(%rbp),%rcx
    #movq 16(%rbp),%rdi

    xorq %rax,%rax

    start_loop:
    addl (%rdi),%eax
    addq $4,%rdi
    loop start_loop

    popq %rcx
    popq %rdi

    movq %rbp , %rsp
    popq %rbp
    ret

Предположих, че C се подчинява на конвенцията за извикване и избутва всички аргументи в стека. И наистина, на позиция 24(%rbp) мога да намеря дължината на масива. Очаквах да намеря указателя към масива на 16(%rbp), но вместо това просто намерих 0x0. След известно отстраняване на грешки открих, че C изобщо не натиска указателя, а вместо това премества целия указател в регистъра %rdi.

Защо това се случва? Не можах да намеря информация за това поведение.


person x95    schedule 08.01.2017    source източник
comment
Ако използвате gcc или clang, можете да подадете '-S', когато компилирате C файл и той ще изхвърли генерирания от него сборник. Това може да ви помогне да отстраните грешки в това, което прави вашият C код.   -  person Irisshpunk    schedule 08.01.2017


Отговори (2)


Конвенцията за извикване, която C компилаторът ще използва, зависи от вашата система, метаданните, които предавате на компилатора, и флаговете. Изглежда, че вашият компилатор използва конвенцията за извикване System V AMD64, описана тук: https://en.m.wikipedia.org/wiki/X86_calling_conventions (което предполага, че използвате Unix-подобна операционна система на 64-битов x86 чип). По принцип в тази конвенция повечето аргументи отиват в регистри, защото е по-бързо и 64-битовите x86 системи имат достатъчно регистри, за да направят това работа (обикновено).

person Irisshpunk    schedule 08.01.2017

Предположих, че C се подчинява на конвенцията за извикване и избутва всички аргументи в стека.

Няма "the" конвенция за повикване. Предаването на аргументи през стека е само една възможна конвенция за извикване (от много). Тази стратегия обикновено се използва на 32-битови системи, но дори и там това не е единственият начин, по който се предават параметрите.

Повечето 64-битови конвенции за извикване предават първите 4–6 аргумента в регистрите, което обикновено е по-ефективно от предаването им в стека.

Коя точно конвенция за повикване е в действие тук зависи от системата; въпросът ви не дава много представа дали използвате Windows или *nix, но предполагам, че използвате *nix, тъй като параметърът се предава в регистъра rdi. В този случай компилаторът ще следва System V AMD64 ABI.

В конвенцията за извикване на System V AMD64 първите шест аргумента с размер на цяло число (които също могат да бъдат указатели) се предават в регистрите RDI, RSI, RDX, RCX, R8 и R9 в този ред. Всеки регистър е предназначен за параметър, като по този начин параметър 1 винаги влиза в RDI, параметър 2 винаги влиза в RSI и т.н. Вместо това параметрите с плаваща запетая се предават през векторните регистри, XMM0-XMM7. Допълнителните параметри се предават на стека в обратен ред.

Повече информация за тази и други обичайни конвенции за повикване е достъпна в x86 етикет wiki.

person Cody Gray    schedule 08.01.2017