2010-05-06 26 views
7

Tôi đã viết một chương trình Hello World đơn giản.Nghi ngờ trong tệp đối tượng thực thi và có thể định vị lại

#include <stdio.h> 
    int main() { 
    printf("Hello World"); 
    return 0; 
    } 

Tôi muốn hiểu cách tệp đối tượng có thể định vị lại và tệp thực thi trông như thế nào. Đối tượng tập tin tương ứng với chức năng chính là

0000000000000000 <main>: 
    0: 55      push %rbp 
    1: 48 89 e5    mov %rsp,%rbp 
    4: bf 00 00 00 00   mov $0x0,%edi 
    9: b8 00 00 00 00   mov $0x0,%eax 
    e: e8 00 00 00 00   callq 13 <main+0x13> 
    13: b8 00 00 00 00   mov $0x0,%eax 
    18: c9      leaveq 
    19: c3      retq 

Ở đây, gọi hàm cho printf là callq 13. Một điều tôi không hiểu là tại sao nó 13. Điều đó có nghĩa gọi hàm ở adresss 13, phải không ??. 13 có hướng dẫn tiếp theo, phải không ?? Xin giải thích cho tôi điều này có nghĩa là gì ??

Các mã thực thi tương ứng với chính là

00000000004004cc <main>: 
    4004cc:  55      push %rbp 
    4004cd:  48 89 e5    mov %rsp,%rbp 
    4004d0:  bf dc 05 40 00   mov $0x4005dc,%edi 
    4004d5:  b8 00 00 00 00   mov $0x0,%eax 
    4004da:  e8 e1 fe ff ff   callq 4003c0 <[email protected]> 
    4004df:  b8 00 00 00 00   mov $0x0,%eax 
    4004e4:  c9      leaveq 
    4004e5:  c3      retq 

Dưới đây là callq 4003c0. Nhưng lệnh nhị phân là e8 e1 fe ff ff. Không có gì tương ứng với 4003c0. Điều gì là tôi đang nhận được sai?

Cảm ơn. Bala

Trả lời

7

Trong trường hợp đầu tiên, hãy xem mã hóa lệnh - đó là tất cả các số không nơi địa chỉ hàm sẽ xuất hiện. Đó là bởi vì đối tượng chưa được liên kết, vì vậy các địa chỉ cho các ký hiệu bên ngoài vẫn chưa được kết nối. Khi bạn thực hiện liên kết cuối cùng vào định dạng thực thi, hệ thống sẽ giữ một trình giữ chỗ khác trong đó và sau đó liên kết động cuối cùng sẽ thêm địa chỉ chính xác cho printf() khi chạy. Đây là một ví dụ nhanh cho chương trình "Hello, world" mà tôi đã viết.

Thứ nhất, tháo gỡ các đối tượng tập tin:

00000000 <_main>: 
    0: 8d 4c 24 04    lea 0x4(%esp),%ecx 
    4: 83 e4 f0    and $0xfffffff0,%esp 
    7: ff 71 fc    pushl -0x4(%ecx) 
    a: 55      push %ebp 
    b: 89 e5     mov %esp,%ebp 
    d: 51      push %ecx 
    e: 83 ec 04    sub $0x4,%esp 
    11: e8 00 00 00 00   call 16 <_main+0x16> 
    16: c7 04 24 00 00 00 00 movl $0x0,(%esp) 
    1d: e8 00 00 00 00   call 22 <_main+0x22> 
    22: b8 00 00 00 00   mov $0x0,%eax 
    27: 83 c4 04    add $0x4,%esp 
    2a: 59      pop %ecx 
    2b: 5d      pop %ebp 
    2c: 8d 61 fc    lea -0x4(%ecx),%esp 
    2f: c3      ret  

Sau đó, di dời:

main.o:  file format pe-i386 

RELOCATION RECORDS FOR [.text]: 
OFFSET TYPE    VALUE 
00000012 DISP32   ___main 
00000019 dir32    .rdata 
0000001e DISP32   _puts 

Như bạn có thể thấy có một di dời có cho _puts, đó là những gì các cuộc gọi đến printf quay vào. Việc di chuyển đó sẽ được chú ý tại thời gian liên kết và cố định. Trong trường hợp liên kết thư viện động, việc di dời và sửa chữa có thể không được giải quyết hoàn toàn cho đến khi chương trình đang chạy, nhưng bạn sẽ có ý tưởng từ ví dụ này, tôi hy vọng.

+0

Bất kỳ nhận xét nào từ trình gỡ xuống? –

5

Cuộc gọi có liên quan trong x86, IIRC nếu bạn có e8, vị trí cuộc gọi là addr + 5.

e1 fe ff ff a là bước nhảy tương đối nhỏ được mã hóa cuối. Nó thực sự có nghĩa là fffffee1.

Bây giờ thêm này đến địa chỉ của lệnh gọi + 5: (0xfffffee1 + 0x4004da + 5) % 2**32 = 0x4003c0

+1

+5 là vì nó liên quan đến lệnh * kế tiếp * sau cuộc gọi và cuộc gọi dài 5 byte. – caf

+0

Các cuộc gọi trên x86 có thể là tương đối hoặc tuyệt đối. Nó chỉ là 'E8' là một cuộc gọi tương đối. – AnT

+0

Vâng tôi quên cũng có những điểm đến tuyệt đối, nhưng chúng được chỉ định bởi phân đoạn: bộ chọn hoặc con trỏ đến một địa chỉ để chuyển đến. –

7

Mục tiêu của cuộc gọi trong hướng dẫn E8 (call) được quy định như tương đối bù đắp từ con trỏ lệnh hiện thời (IP) giá trị.

Trong mẫu mã đầu tiên của bạn, bù đắp rõ ràng là 0x00000000. Về cơ bản nó nói

call +0 

Địa chỉ thực tế của printf hiện chưa được giải, vì vậy trình biên dịch chỉ cần đặt giá trị 32-bit 0x00000000 đó như một giữ chỗ.

Cuộc gọi không đầy đủ như vậy với bù trừ 0 sẽ tự động được hiểu là cuộc gọi đến giá trị IP hiện tại. Trên nền tảng của bạn, IP được tăng trước, nghĩa là khi một số lệnh được thực hiện, IP chứa địa chỉ của lệnh tiếp theo. I E. khi lệnh tại địa chỉ 0xE được thực hiện, IP chứa giá trị 0x13. Và call +0 được diễn giải một cách tự nhiên như lời gọi để hướng dẫn 0x13. Đây là lý do tại sao bạn thấy rằng 0x13 trong việc tháo gỡ mã không đầy đủ.

Khi mã hoàn tất, phần giữ chỗ 0x00000000 offset được thay thế bằng độ lệch thực tế của hàm printf trong mã. Giá trị offset có thể dương (chuyển tiếp) hoặc âm (ngược). Trong trường hợp của bạn, IP tại thời điểm cuộc gọi là 0x4004DF, trong khi địa chỉ của hàm printf0x4003C0. Vì lý do này, lệnh máy sẽ chứa giá trị bù đắp 32 bit bằng 0x4003C0 - 0x4004DF, giá trị âm là -287. Vì vậy, những gì bạn thấy trong mã thực tế là

call -287 

-2870xFFFFFEE1 trong nhị phân. Đây chính xác là những gì bạn thấy trong mã máy của bạn. Nó chỉ là công cụ bạn đang sử dụng hiển thị nó ngược.

Các vấn đề liên quan