2017-10-16 22 views
0

스택 오버 플로우와 같은 커뮤니티에 액세스 할 수있는 컴퓨터 프로그래밍 기술을 배우는 데는 모두 다행 스럽습니다! 나는 컴퓨터를 프로그래밍하는 법을 배우는 일을 맡기로 결정했다. 그리고 나는 'Programming From the Ground Up'이라는 전자 책에 대한 지식으로 독자들에게 어셈블리 언어로 프로그램을 만드는 법을 가르치고있다. GNU/Linux 환경에서.모든 레지스터 이름을 eXX에서 rXX로 변경하여 32에서 64 비트로 이식하면 팩토리얼이 0을 반환합니까?

GCC의 어셈블러로 인한 오류없이 실행 한 함수를 사용하여 정수 4의 계승을 계산하는 프로그램을 만들었습니다. 프로그램. 그러나, 내 프로그램의 함수는 올바른 대답을 반환하지 않습니다! 4의 계승은 24이지만 프로그램은 0의 값을 반환합니다! 맞습니다. 왜 그런지 모르겠습니다.

.section .data 

.section .text 

.globl _start 

.globl factorial 

_start: 

push $4     #this is the function argument 
call factorial    #the function is called 
add $4, %rsp    #the stack is restored to its original 
          #state before the function was called 
mov %rax, %rbx    #this instruction will move the result 
          #computed by the function into the rbx 
          #register and will serve as the return 
          #value 
mov $1, %rax    #1 must be placed inside this register for 
          #the exit system call 
int $0x80     #exit interrupt 

.type factorial, @function #defines the code below as being a function 

factorial:     #function label 
push %rbp     #saves the base-pointer 
mov %rsp, %rbp    #moves the stack-pointer into the base- 
          #pointer register so that data in the stack 
          #can be referenced as indexes of the base- 
          #pointer 
mov $1, %rax    #the rax register will contain the product 
          #of the factorial 
mov 8(%rbp), %rcx   #moves the function argument into %rcx 
start_loop:    #the process loop begins 
cmp $1, %rcx    #this is the exit condition for the loop 
je loop_exit    #if the value in %rcx reaches 1, exit loop 
imul %rcx, %rax   #multiply the current integer of the 
          #factorial by the value stored in %rax 
dec %rcx     #reduce the factorial integer by 1 
jmp start_loop    #unconditional jump to the start of loop 
loop_exit:     #the loop exit begins 
mov %rbp, %rsp    #restore the stack-pointer 
pop %rbp     #remove the saved base-pointer from stack 
ret      #return 
+4

** 컴퓨터 프로그래밍의 기술을 배우는 여러분 모두 ** 디버거 **에 액세스하는 것이 얼마나 행운입니까? 따라서 코드를 사용하여 코드가 잘못되는 부분을 찾으십시오. 힌트 : 이미 인수에 액세스하지 못했습니다. 추신 : x86-64 규칙은 인수 전달에 스택을 사용하지 않지만 올바르게 수행하면 스택을 사용할 수 있습니다. PPS : 64 비트 코드에서'int 0x80'을 사용하지 마십시오. 그것은 여기에서 일하는 것을 일어난다. – Jester

+0

코드에 몇 가지 설명을 추가 할 수 있습니까? 프로그램이 기대하는 논거는 무엇입니까? - [book] (http://mirror.cedia.org.ec/nongnu/pgubook/ProgrammingGroundUp-1-0-booksize.pdf)의 사례는 모든 레지스터 대신 레지스터의 일부를 사용합니다 (예 : % rsp 대신 esp. - Github에서 반복적이고 재귀적인 [요인 구현] (https://github.com/bbyars/programming-from-the-ground-up/tree/master/ch4-functions)을 확인할 수 있습니다. . – Jaime

+1

@Jaime 책의 예제는 x86 32b 어셈블리를위한 것입니다. 예를 들어, OP가 64b 변형과 레지스터 이름을 바꿔 가며 순진한 방식으로 x86-64로 변환하려고 한 것처럼 보입니다. 그래서 분명히 그 코드는 잘못된 모드로 실행되기 때문에 올바르게 작동 할 기회가 없습니다. OP : 책을 제대로 따라 가십시오. 64 바이트 리눅스에서 32 비트 바이너리를 컴파일하고 디버깅하는 방법을 찾아라. – Ped7g

답변

5

TL : 여기

이 고려에 대한 코드입니다 DR : 당신이 잘못 이식하기 때문에 리턴 어드레스의 계승이 0를 떠나, %rax 넘쳐. 64 비트로


이식 32 비트 코드는 모든 레지스터 이름을 변경하는 것만 큼 간단하지 않다. 이렇게하면 어셈블 할 수 있지만,이 간단한 프로그램도 다른 방식으로 작동합니다. x86-64에서 push %regcall은 모두 64 비트 값을 밀어 넣고 rsp을 8로 수정합니다. 디버거로 코드를 한 단계 밟으면이 값이 표시됩니다. 당신은 32 비트 예제를 사용하는 책을 다음있어

을 (. ASM에 대한 gdb을 사용하여 정보에 대한 x86 tag wiki의 하단을 참조), 그래서 당신은 아마 단지 build them as 32-bit executables 대신 64 비트에 포트 그들에게 노력한다 어떻게 알기도 전에. 64 비트 포인터를 전달하려고하면


귀하의 sys_exit()int 0x80 ABI가 여전히 작동하는 32 비트 (What happens if you use the 32-bit int 0x80 Linux ABI in 64-bit code?)를 사용하지만, 시스템에 문제가 발생하게됩니다 호출합니다. Use the 64-bit ABI.

당신이 어떤 라이브러리 함수를 호출 할 경우 표준 함수 호출 규칙이 다르기 때문에 또한도 문제로 실행됩니다. , 그리고 64 비트 ABI 링크 및 태그 위키에있는 다른 호출 규칙 문서를 참조하십시오.


하지만 당신은 그 중 하나를 수행하지 않는, 그래서 프로그램의 문제는 단순히 - 64의 두 배 "스택 폭"를 차지하지로 내려 온다. factorial 함수는 반환 주소를 인수로으로 읽습니다. 여기

은 코드, 그것은 실제로 모든 인수를 전달 당신이 발명 한 표준이 아닌 64 비트 호출 대회 (
push $4     # rsp-=8. (rsp) = qword 4 
          # non-standard calling convention with args on the stack. 
call factorial    # rsp-=8. (rsp) = return address. RIP=factorial 
add $4, %rsp    # misalign the stack, so it's pointing to the top half of the 4 you pushed earlier. 
# if this was in a function that wanted to return, you'd be screwed. 

mov %rax, %rbx    # copy return value to first arg of system call 
mov $1, %rax    #eax = __NR_EXIT from asm/unistd_32.h, wasting 2 bytes vs. mov $1, %eax 
int $0x80     # 32-bit ABI system call, eax=call number, ebx=first arg. sys_exit(factorial(4)) 

그래서 발신자 일종의 괜찮 무엇을 설명하는 주석 스택). add에서 %rsp까지 완전히 생략 할 수 있습니다. 스택을 더 이상 터치하지 않고 종료하려고하기 때문에.정적 실행 (동적으로 연결된 실행 that aren't ASLR enabled with PIE) 용

.type factorial, @function #defines the code below as being a function 

factorial:     #function label 
push %rbp     #rsp-=8, (rsp) = rbp 
mov %rsp, %rbp    # make a traditional stack frame 

mov $1, %rax    #retval = 1. (Wasting 2 bytes vs. the exactly equivalent mov $1, %eax) 

mov 8(%rbp), %rcx   #load the return address into %rcx 

... and calculate the factorial 

, _start0x4000c0에서 일반적이다. 0x4000c0 * 3c 대기 시간이 imul이기 때문에 프로그램은 여전히 ​​최신 CPU에서 거의 순간적으로 작동합니다. 여전히 1250 만 코어 클럭 사이클입니다. 4GHz CPU에서 3 밀리 초의 CPU 시간입니다.

당신이 최근의 배포판에 gcc foo.o와 연결하여 위치 독립적 인 실행 파일을 만든했다면

_start0x5555555545a0 같은 주소를 가질 것이며, 함수 3주기와 4GHz의 CPU에서 실행 ~ 70,368초 걸렸을 것 imul 대기 시간.

4194496! 많은 숫자가 짝수이기 때문에 이진 표현의 길이는 입니다. 전체 %rax은 (당신이 리눅스 프로세스의 종료 상태는 sys_exit()에 전달 정수의 하위 8 비트 1.

에 아래 0x4000c0에서 모든 수를 곱을 완료 시간에 의해 제로가 될 것입니다 때문에 wstatus은 32 비트 int이며 다른 신호를 포함합니다. 어떤 신호가 프로세스를 종료했는지와 같습니다 (wait4(2) 참조). 그래서 작은 args와 함께, 그것은 많이 걸리지 않습니다.