programing

수동 조립 대 GCC

jooyons 2023. 9. 27. 17:55
반응형

수동 조립 대 GCC

면책 사항:이제 막 x86 조립을 시작합니다.대학에서 SPIM을 조금 배웠지만, 그것은 거의 언급할 가치가 없습니다.

libc에서 가장 간단한 기능인 abs()부터 시작하려고 생각했습니다.C에서 꽤 간단합니다.

long myAbs(long j) {
    return j < 0 ? -j : j;
}

조립 중인 내 버전:

    .global myAbs
    .type   myAbs, @function
    .text

myAbs:
    test %rdi, %rdi
    jns end
    negq %rdi
end:
    movq %rdi, %rax
    ret

(이것은 32비트 정수에서는 작동하지 않습니다. 아마도 RAX가 64비트 레지스터이고 기호가 잘못된 위치에 있기 때문일 것입니다. - 조사해야 합니다.)

이제 gcc가 하는 일은 다음과 같습니다(gcc-O2-SmyAbs.c).

        .file   "myAbs.c"
        .section        .text.unlikely,"ax",@progbits
.LCOLDB0:
        .text
.LHOTB0:
        .p2align 4,,15
        .globl  myAbs
        .type   myAbs, @function
myAbs:
.LFB0:
        .cfi_startproc
        pushq   %rbp
        .cfi_def_cfa_offset 16
        .cfi_offset 6, -16
        movq    %rsp, %rbp
        .cfi_def_cfa_register 6
        subq    $4144, %rsp
        orq     $0, (%rsp)
        addq    $4128, %rsp
        movq    %rdi, %rdx
        sarq    $63, %rdx
        movq    %fs:40, %rax
        movq    %rax, -8(%rbp)
        xorl    %eax, %eax
        movq    %rdi, %rax
        xorq    %rdx, %rax
        subq    %rdx, %rax
        movq    -8(%rbp), %rcx
        xorq    %fs:40, %rcx
        jne     .L5
        leave
        .cfi_remember_state
        .cfi_def_cfa 7, 8
        ret
.L5:
        .cfi_restore_state
        call    __stack_chk_fail@PLT
        .cfi_endproc
.LFE0:
        .size   myAbs, .-myAbs
        .section        .text.unlikely
.LCOLDE0:
        .text
.LHOTE0:
        .ident  "GCC: (Gentoo Hardened 5.1.0 p1.2, pie-0.6.3) 5.1.0"
        .section        .note.GNU-stack,"",@progbits

왜 이렇게 큰 차이가 나는 거지?GCC는 훨씬 더 많은 명령어를 생산합니다.이것이 내 코드보다 느리리라고는 상상할 수 없습니다.내가 뭘 빼놓았나요?아니면 제가 여기서 뭔가 심각한 잘못을 하고 있는 건가요?

생성된 코드가 무엇인지 궁금해하는 사람들을 위해, 먼저 GCC가 컴파일 할 때 주목하십시오.myAbs스택 보호 기능을 통해 이러한 형태로 변환

long myAbs(long j) {
    uintptr_t canary = __stack_chk_guard;

    register long result = j < 0 ? -j : j;

    if ( (canary = canary ^ __stack_chk_guard) != 0 )
        __stack_chk_fail();
}

간단히 수행할 코드j < 0 ? -j : j;

movq    %rdi, %rdx     ;RDX = j
movq    %rdi, %rax     ;RAX = j
sarq    $63, %rdx      ;RDX = 0 if j >=0, 0fff...ffh if j < 0
xorq    %rdx, %rax     ;Note: x xor 0ff...ffh = Not X, x xor 0 = x
                       ;RAX = j if j >=0, ~j if j < 0
subq    %rdx, %rax     ;Note: 0fff...ffh = -1
                       ;RAX = j+0 = j if j >= 0, ~j+1 = -j if j < 0
                       ;~j+1 = -j in two complement

우리가 얻은 코드를 분석하는 것은

    pushq   %rbp
    movq    %rsp, %rbp       ;Standard prologue

    subq    $4144, %rsp      ;Allocate slight more than 4 KiB     
    orq     $0, (%rsp)       ;Perform a useless RW operation to test if there is enough stack space for __stack_chk_fail

    addq    $4128, %rsp      ;This leave 16 byte allocated for local vars

    movq    %rdi, %rdx       ;See above
    sarq    $63, %rdx        ;See above

    movq    %fs:40, %rax     ;Get the canary
    movq    %rax, -8(%rbp)   ;Save it as a local var
    xorl    %eax, %eax       ;Clear it

    movq    %rdi, %rax       ;See above
    xorq    %rdx, %rax       ;See above
    subq    %rdx, %rax       ;See above

    movq    -8(%rbp), %rcx   ;RCX = Canary
    xorq    %fs:40, %rcx     ;Check if equal to the original value
    jne     .L5              ;If not fail

    leave
    ret
.L5:
    call    __stack_chk_fail@PLT  ;__stack_chk_fail is noreturn

따라서 스택 스매싱 프로텍터를 구현하기 위한 추가 지침이 모두 제공됩니다.

프롤로그 후 첫번째 지시사항 사용을 지적해주신 FUZxxl님께 감사드립니다.

대부분의 초기 호출은 스택을 설정하고 반환 주소를 저장하는 것입니다.스택 보호 작업이 진행되고 있는 것 같습니다.오버헤드를 제거하기 위해 컴파일러 설정을 조정할 수도 있습니다.

컴파일러에 다음과 같은 플래그를 추가할 수 있습니다.-fno-stack-protector이 차이를 최소화할 수 있습니다.

예, 이것은 아마도 손으로 쓴 조립품보다 느리겠지만, 훨씬 더 많은 보호 기능을 제공하며, 약간의 오버헤드에 비해 가치가 있을 것입니다.

스택 보호 기능이 리프 함수임에도 불구하고 여전히 존재하는 이유는 여기를 참조하십시오.

언급URL : https://stackoverflow.com/questions/31539725/manual-assembly-vs-gcc

반응형