Действительно ли memcpy работает с символом? - PullRequest
0 голосов
/ 09 июля 2020

Этот простой c:

#include <stdio.h>
#include <string.h>
int *add(int a, int b){
    int ar[1];
    int result = a+b;
    memcpy(ar, &result, sizeof(int));
    return ar;
}

int main(){
    int a = add(1,2)[0];
    printf("%i\n",a);
}

компилируется в это:

.text
    .globl  add
    .type   add, @function
add:
    pushq   %rbp    #
    movq    %rsp, %rbp  #,
    movl    %edi, -20(%rbp) # a, a
    movl    %esi, -24(%rbp) # b, b
# a.c:5:    int result = a+b;
    movl    -20(%rbp), %edx # a, tmp91
    movl    -24(%rbp), %eax # b, tmp92
    addl    %edx, %eax  # tmp91, _1
# a.c:5:    int result = a+b;
    movl    %eax, -8(%rbp)  # _1, result
# a.c:6:    memcpy(ar, &result, sizeof(int)); ---I SEE NO CALL INSTRUCTION---
    movl    -8(%rbp), %eax  # MEM[(char * {ref-all})&result], _6
    movl    %eax, -4(%rbp)  # _6, MEM[(char * {ref-all})&ar]
# a.c:7:    return ar;
    movl    $0, %eax    #--THE FUNCTION SHOULD RETURN ADDRESS OF ARRAY, NOT 0. OTHERWISE command terminated
#   lea -4(%rbp), %rax  #--ONLY THIS IS CORRECT, NOT `0`
# a.c:8: }
    popq    %rbp    #
    ret 
    .size   add, .-add
    .section    .rodata
.LC0:
    .string "%i\n"
    .text
    .globl  main
    .type   main, @function
main:
    pushq   %rbp    #
    movq    %rsp, %rbp  #,
    subq    $16, %rsp   #,
# a.c:11:   int a = add(1,2)[0];
    movl    $2, %esi    #,
    movl    $1, %edi    #,
    call    add #
# a.c:11:   int a = add(1,2)[0];
    movl    (%rax), %eax    # *_1, tmp90
    movl    %eax, -4(%rbp)  # tmp90, a
# a.c:12:   printf("%i\n",a);
    movl    -4(%rbp), %eax  # a, tmp91
    movl    %eax, %esi  # tmp91,
    leaq    .LC0(%rip), %rdi    #,
    movl    $0, %eax    #,
    call    printf@PLT  #
    movl    $0, %eax    #, _6
# a.c:13: }
    leave   
    ret 
    .size   main, .-main
    .ident  "GCC: (Debian 8.3.0-6) 8.3.0"
    .section    .note.GNU-stack,"",@progbits

Все функции из stdlib, например printf или puts, call взяты из GOT (т.е. регистр %rip содержит адрес GOT). Но не memcpy, это похоже на «встроенные инструкции сборки» вместо обычного адреса вызова. Так является ли memcpy символом? Если да, то почему это не аргумент для call? Есть ли memcpy в таблице GOT? Если да, то какое смещение от GOT к этому символу?

1 Ответ

5 голосов
/ 09 июля 2020

Итак, во-первых, у вас есть ошибка:

$ cc -O2 -S test.c
test.c: In function ‘add’:
test.c:7:12: warning: function returns address of local variable

Возврат адреса локальной переменной имеет неопределенное поведение, тогда и только тогда, когда вызывающий использует это значение; вот почему ваш компилятор сгенерировал код, который возвратил нулевой указатель, который sh сканирует программу, если используется, но безвреден в противном случае. Фактически, моя копия G CC генерирует только это для add:

add:
        xorl    %eax, %eax
        ret

, потому что такая обработка возвращаемого значения делает другие операции в add мертвым кодом.

(Ограничение «только если используется» также является причиной того, что мой компилятор выдает предупреждение, а не серьезную ошибку.)

Теперь, если я изменю вашу программу, чтобы она имела четко определенное поведение, например

#include <stdio.h>
#include <string.h>

void add(int *sum, int a, int b)
{
    int result = a+b;
    memcpy(sum, &result, sizeof(int));
}

int main(void)
{
    int a;
    add(&a, 1, 2);
    printf("%i\n",a);
    return 0;
}

тогда я действительно вижу ассемблерный код, в котором вызов memcpy заменен встроенным кодом:

add:
    addl    %edx, %esi
    movl    %esi, (%rdi)
    ret

Это особенность многих современных компиляторов C: они знают, что некоторые из функций библиотеки C это делают и могут встраивать их, когда это имеет смысл. (Вы можете видеть, что в этом случае сгенерированный код меньше и быстрее, чем был бы при фактическом вызове memcpy.)

G CC позволяет мне отключить эту функцию с помощью команды -line option:

$ gcc -O2 -ffreestanding test.c
$ sed -ne '/^add:/,/cfi_endproc/{; /^\.LF[BE]/d; /\.cfi_/d; p; }' test.s
add:
    subq    $24, %rsp
    addl    %edx, %esi
    movl    $4, %edx
    movl    %esi, 12(%rsp)
    leaq    12(%rsp), %rsi
    call    memcpy@PLT
    addq    $24, %rsp
    ret

В этом режиме вызов memcpy в add обрабатывается так же, как вызов printf в main. В вашем компиляторе могут быть похожие параметры.

...