Почему 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 пометить вики.

person Cody Gray    schedule 08.01.2017