2010-03-23 4 views
14

흥미로운 문제가 있습니다. 64 비트 시스템 & OS를 사용하고 32 비트 어셈블리 코드를 작성하는 것을 잊어 버렸습니다. 64 비트 코드를 작성하는 방법을 모르겠습니다.64 비트 Linux 및 64 비트 프로세서에서 32 비트 어셈블리 코드 실행하기 : 이상 현상을 설명하십시오.

Linux의 Gnu 어셈블러 (AT & T 구문) 용 x86 32 비트 어셈블리 코드입니다.

이제
//hello.S 
#include <asm/unistd.h> 
#include <syscall.h> 
#define STDOUT 1 

.data 
hellostr: 
    .ascii "hello wolrd\n"; 
helloend: 

.text 
.globl _start 

_start: 
    movl $(SYS_write) , %eax //ssize_t write(int fd, const void *buf, size_t count); 
    movl $(STDOUT) , %ebx 
    movl $hellostr , %ecx 
    movl $(helloend-hellostr) , %edx 
    int $0x80 

    movl $(SYS_exit), %eax //void _exit(int status); 
    xorl %ebx, %ebx 
    int $0x80 

    ret 

,이 코드는 & 32 비트 OS 바로 32 비트 프로세서에서 잘 실행해야합니까? 64 비트 프로세서는 32 비트 프로세서와 역 호환됩니다. 그래서, 그것은 또한 문제가되지 않을 것입니다. 이 문제는 64 비트 OS & 32 비트 OS에서 시스템 호출 호출 메커니즘의 차이로 인해 발생합니다. 왜 그런지 모르지만 그들은 32 비트 리눅스 & 64 비트 리눅스 사이의 시스템 호출 번호를 변경했습니다.

ASM은/unistd_32.h 정의

#define __NR_write  4 
#define __NR_exit   1 

ASM/unistd_64.h는 정의

#define __NR_write    1 
#define __NR_exit    60 

어쨌든 대신 직접 숫자의 매크로를 사용하여 돈을 지불한다. 올바른 시스템 호출 번호를 보장합니다.

내가 조립하면 & 링크 & 프로그램을 실행합니다.

$cpp hello.S hello.s //pre-processor 
$as hello.s -o hello.o //assemble 
$ld hello.o // linker : converting relocatable to executable 

인쇄되지 않음 helloworld.

은 GDB에서의 전시는 :

  • 프로그램은 내가 GDB로 디버깅하는 방법을 모르는 코드 01

로 종료. 튜토리얼을 사용하여 디버깅을 시도하고 각 단계에서 레지스터 검사 명령으로 명령을 실행했습니다. 항상 "나와 함께 프로그램을 종료 01". 몇 가지 방법으로이 문제를 디버깅하는 방법을 보여 주면 좋을 것입니다.

(gdb) break _start 
Note: breakpoint -10 also set at pc 0x4000b0. 
Breakpoint 8 at 0x4000b0 
(gdb) start 
Function "main" not defined. 
Make breakpoint pending on future shared library load? (y or [n]) y 
Temporary breakpoint 9 (main) pending. 
Starting program: /home/claws/helloworld 

Program exited with code 01. 
(gdb) info breakpoints 
Num  Type   Disp Enb Address   What 
8  breakpoint  keep y 0x00000000004000b0 <_start> 
9  breakpoint  del y <PENDING>   main 

나는 strace을 실행 해 보았습니다.

execve("./helloworld", ["./helloworld"], [/* 39 vars */]) = 0 
write(0, NULL, 12 <unfinished ... exit status 1> 
  1. 이 strace를의 출력에 write(0, NULL, 12) 시스템 호출의 매개 변수 설명 :이 출력입니까?
  2. 무엇 정확히이보고 있나요? 정확히이 exitstatus = 1로 종료되는 이유를 알고 싶습니다.
  3. gdb를 사용하여이 프로그램을 디버깅하는 방법을 보여줄 수 있습니까?
  4. 시스템 호출 번호가 변경된 이유는 무엇입니까?
  5. 이 컴퓨터에서이 프로그램을 올바르게 실행할 수 있도록이 프로그램을 적절하게 변경하십시오.

편집 :

폴 R의 답변을 읽은 후. 나는 내 파일 I이 32 비트 재배치 & 실행 ELF해야한다는 그에게 동의

[email protected]:~$ file ./hello.o 
./hello.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped 

[email protected]:~$ file ./hello 
./hello: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, not stripped 

를 확인했습니다. 그러나 그것은 나의 제 질문에 답하지 않습니다. 내 모든 질문은 여전히 ​​질문입니다. 이 사건에서 정확히 무슨 일이 일어 났습니까? 누군가 내 질문에 답하고이 코드의 x86-64 버전을 제공 할 수 있습니까?

답변

7

기본적으로 64 비트 OS의 모든 것이 64 비트라고 가정합니다. 32 비트 버전의 #include (적절한 경우) (b) 32 비트 라이브러리와 링크 (c) 32 비트 실행 파일 만들기 (해당하는 경우)를 수행해야합니다. makefile을 가지고 있다면 makefile의 내용을 보여 주거나이 예제를 빌드하는 데 사용하는 명령을 사용하면 도움이 될 것입니다.

FWIW 나는 약간의 코드를 변경 (_start -> 주) :

#include <asm/unistd.h> 
#include <syscall.h> 
#define STDOUT 1 

    .data 
hellostr: 
    .ascii "hello wolrd\n" ; 
helloend: 

    .text 
    .globl main 

main: 
    movl $(SYS_write) , %eax //ssize_t write(int fd, const void *buf, size_t count); 
    movl $(STDOUT) , %ebx 
    movl $hellostr , %ecx 
    movl $(helloend-hellostr) , %edx 
    int $0x80 

    movl $(SYS_exit), %eax //void _exit(int status); 
    xorl %ebx, %ebx 
    int $0x80 

    ret 

이처럼 내장 :

: 우리가 32 비트 실행 파일을 가지고

$ gcc -Wall test.S -m32 -o test 

verfied

$ file test 
test: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), for GNU/Linux 2.6.4, dynamically linked (uses shared libs), not stripped 

정상적으로 실행되는 것으로 나타납니다.

$ ./test 
hello wolrd 
+0

'_start' 또는'main'의 차이점은 무엇입니까? – claws

+0

@claws : 코드를 gcc와 쉽게 만들고 링크 할 수 있도록 변경 한 것뿐입니다.하지만 main이 호출되기 전에 C 런타임 라이브러리 시작 코드가 실행된다는 것을 의미하는 것으로 생각됩니다. –

+0

내 질문을 편집했습니다. 또한'gcc -Wall test '를 사용하여 코드를 빌드하려고 할 때.S -m32 -o test'이 오류가 발생합니다 (밑줄은 분리 기호로 사용됩니다) : /usr/bin/ld : 호환되지 않는 /usr/lib/gcc/x86_64-linux-gnu/4.4.1/libgcc.a를 건너 뜁니다. -lgcc를 검색 할 때 을 검색 할 때 ______________ /usr/bin/ld : 호환되지 않는 /usr/lib/gcc/x86_64-linux-gnu/4.4.1/libgcc.a를 건너 뛰는 경우 ______________ /usr/bin/ld : ld : -lgcc를 찾을 수 없습니다. ______________ collect2 : ld가 1을 반환했습니다. 종료 상태 – claws

6

Paul이 언급했듯이 64 비트 시스템에서 32 비트 바이너리를 빌드하려면 설치시 기본적으로 사용할 수없는 -m32 플래그를 사용해야합니다 (일부 64 비트 Linux 배포판에는 기본적으로 32 비트 컴파일러/링커/lib 지원이 포함되어 있지 않습니다.

반면에 코드를 64 비트로 빌드 할 수 있습니다.이 경우 64 비트 호출 규칙을 사용해야합니다. 이 경우, 시스템 콜 번호 %의 RAX에 간다, 그리고 인수 %의 RDI %의 RSI,와 % RDX 갈

편집 내가 이것을 발견 한 최고의 장소가 특별히 www.x86-64.org입니다

abi.pdf

+0

64 비트 규칙에 대해 언급 해 주셔서 감사합니다. 나는 필사적으로 그것을 찾고있다. 64 비트 컨벤션에 대해 더 알고 싶습니다. 너 나 좀 연결시켜 줄래? (공식은 더 좋을 것이다). – claws

1

64 비트 CPU는 32 비트 코드를 실행할 수 있지만 특수 모드를 사용해야합니다. 이 지침들은 모두 64 비트 모드에서 유효하기 때문에 64 비트 실행 파일을 만들지 못했습니다.

코드가 올바르게 빌드되어 gcc -m32 -nostdlib hello.S으로 실행됩니다. -m32__i386이므로 /usr/include/asm/unistd.h<asm/unistd_32.h>이 포함되어 있으며 int $0x80 ABI에 적합한 정수가 있기 때문입니다.

main 및 libc가 포함되거나 포함되지 않은 정적 실행 파일과 동적 실행 파일에 대한 자세한 내용은 Assembling 32-bit binaries on a 64-bit system (GNU toolchain)을 참조하십시오.당신이 바로 전화 번호를 사용했던 경우

$ file a.out 
a.out: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), statically linked, BuildID[sha1]=973fd6a0b7fa15b2d95420c7a96e454641c31b24, not stripped 

$ strace ./a.out > /dev/null 
execve("./a.out", ["./a.out"], 0x7ffd43582110 /* 64 vars */) = 0 
strace: [ Process PID=2773 runs in 32 bit mode. ] 
write(1, "hello wolrd\n", 12)   = 12 
exit(0)         = ? 
+++ exited with 0 +++ 

기술적으로, 코드뿐만 아니라 64 비트 모드에서 작동하도록 일어날 것 : What happens if you use the 32-bit int 0x80 Linux ABI in 64-bit code? 그러나 int 0x80는 64 비트 코드에서 사용하지 않는 것이 좋습니다. 실제로 32 비트 코드는 커널의 내 보낸 VDSO 페이지를 통해 호출해야하므로이를 지원하는 CPU에서 빠른 시스템 호출을 위해 sysenter을 사용할 수 있습니다.


하지만 내 내 질문에 대답하지 않습니다. 이 경우 정확히 입니까?

좋은 질문입니다. eax=1 리눅스에

, int $0x80에 관계없이, sys_exit(ebx) 어떤 모드 호출 프로세스가 있었다. 32 비트 ABI는 (커널은 i386을 ABI 지원없이 컴파일하지 않는 한) 64 비트 모드에서 사용할 수 있지만 그것을 사용하지 마십시오. 종료 상태는 movl $(STDOUT), %ebx입니다.

은 (BTW, unistd.h에 정의 된 STDOUT_FILENO 매크로있다,하지만 당신은 .S에서 #include <unistd.h>는 또한 유효 ASM 구문없는 C 프로토 타입을 포함 할 수 없기 때문에.)

공지 사항 unistd_32.h__NR_write에서 __NR_exitunistd_64.h 모두 1이므로 첫 번째int $0x80이 처리 과정을 종료합니다. 호출중인 ABI에 대해 잘못된 시스템 호출 번호를 사용하고 있습니다. 당신이 (즉, ABI 때문에 64 비트 프로세스가 사용할 것으로 예상되는) syscall를 호출했던 것처럼


strace 것은, 그것을 잘못을 디코딩한다. What are the calling conventions for UNIX & Linux system calls on x86-64

eax=1/syscallwrite(rd=edi, buf=rsi, len=rdx)을 의미하며, strace가 잘못 int $0x80를 디코딩하는 방법이있다.

rdirsi0 (일명 NULL) _start 입장에 있으며, 코드는 movl $(helloend-hellostr) , %edxrdx=12 설정합니다.

리눅스는 execve 이후의 새로운 프로세스에서 레지스터를 0으로 초기화합니다. (ABI는 undefined라고 말하며, Linux는 정보 유출을 피하기 위해 0을 선택합니다). 정적으로 링크 된 실행 파일에서 실행되는 첫 번째 사용자 공간 코드는 _start입니다. 동적 실행 파일에서 동적 링커는 _start 전에 실행되고 레지스터에 가비지가 남습니다.

자세한 asm 링크는 태그 위키를 참조하십시오.

+0

견고한 대답, excve 후에 레지스터를 0으로 설정하지 않는다는 의미를 설명 할 수 있습니까? 그게 어떻게 해가 될 수 있니? – Trey

+1

@Trey : 커널은 신뢰할 수없는 사용자 공간 프로세스에 커널 데이터를 유출하고 싶지 않습니다. Linux는 사용자를 서로 격리시키는 데 신경을 쓰는 다중 사용자 OS이며 암호 같은 일부 중요한 정보 (예 : 커널 취약점을 악용하려는 공격자에게 유용한 메모리 주소)가 레지스터에 주위에 누워있을 일이. –

+0

64 비트 컴퓨터에서 ** int $ 0x80 ** ** rax ** 레지스터가 60 인 경우 어떻게됩니까? 방금 시도했는데, 그게 SIGSEV를주고 있는데, 왜 그런가요? – Trey

관련 문제