2011-11-13 9 views
5

Tôi muốn biết nếu có ai có thể giúp tôi với một vấn đề tôi đang gặp khi học một trong các bài giảng từ một lớp lắp ráp giới thiệu mà tôi đang học ở trường. Vấn đề tôi đang gặp không phải là sự hiểu biết về lắp ráp, đó là cách chính xác mã nguồn C được đặt hàng dựa trên sự lắp ráp. Tôi sẽ đăng đoạn tôi đang nói và có thể nó sẽ rõ ràng hơn những gì tôi đang nói.Kỹ thuật đảo ngược Mã nguồn C từ lắp ráp

C Nguồn cung cấp:

int arith(int x, int y, int z) 
{ 
    int t1 = x+y; 
    int t2 = z+t1; 
    int t3 = x+4; 
    int t4 = y * 48; 
    int t5 = t3 + t4; 
    int rval = t2 * t5; 
    return rval; 
} 

hội đưa ra:

arith: 
pushl %ebp 
movl %esp,%ebp 

movl 8(%ebp),%eax 
movl 12(%ebp),%edx 
leal (%edx,%eax),%ecx 
leal (%edx,%edx,2),%edx 
sall $4,%edx 
addl 16(%ebp),%ecx 
leal 4(%edx,%eax),%eax 
imull %ecx,%eax 

movl %ebp,%esp 
popl %ebp 
ret 

Tôi chỉ bối rối như thế nào tôi nghĩ để có thể phân biệt ví dụ rằng thêm của z + t1 (z + x + y) được liệt kê trên dòng thứ hai (trong nguồn) khi lắp ráp, nó xuất hiện sau mã y * 48 trong mã lắp ráp hoặc ví dụ x + 4 là dòng thứ 3 khi lắp ráp, nó không nằm trong một dòng, loại hỗn hợp với câu lệnh leal cuối cùng. Điều đó có ý nghĩa đối với tôi khi tôi có nguồn nhưng tôi cho rằng có thể tái tạo nguồn cho thử nghiệm và tôi hiểu rằng trình biên dịch tối ưu hóa mọi thứ nhưng nếu có ai đó có cách suy nghĩ về kỹ thuật đảo ngược có thể giúp tôi Tôi sẽ đánh giá rất cao nếu họ có thể hướng dẫn tôi qua quá trình suy nghĩ của họ.

Cảm ơn.

+0

cấp độ bạn đang sử dụng khi bạn đang soạn thảo tối ưu hóa gì? Nếu bạn muốn chuyển đổi theo từng dòng, hãy sử dụng -O0, nếu không, bạn sẽ cần phải tính đến các tối ưu hóa. – Maz

+1

... và đảo ngược mã được tối ưu hóa trở lại thứ tự lệnh C ban đầu không hoàn toàn có thể. –

+0

Bạn có chắc đây là một trình biên dịch ngây thơ không? Có vẻ như nó đã được tối ưu hóa một chút. Viết ra biểu thức đại số mà hàm tính toán và xem liệu bạn có thể khám phá ra điều đó không. –

Trả lời

9

Tôi đã chia nhỏ quá trình tháo gỡ để bạn biết cách lắp ráp được tạo ra từ nguồn C.

8(%ebp) = x, 12(%ebp) = y, 16(%ebp) = z

arith: 

Tạo khung stack:

pushl %ebp 
movl %esp,%ebp 


Move x vào eax, y vào edx:

movl 8(%ebp),%eax 
movl 12(%ebp),%edx 


t1 = x + y. leal (địa chỉ hiệu quả Load) sẽ thêm edxeax, và t1 sẽ được ở ecx:

leal (%edx,%eax),%ecx 


int t4 = y * 48; theo hai bước dưới đây, nhân với 3, sau đó bằng 16. t4 cuối cùng sẽ được edx :

Nhân edx cho 2 và thêm edx vào kết quả, ví dụ: edx = edx * 3:

leal (%edx,%edx,2),%edx 

Shift left 4 bits, ie. nhân với 16:

sall $4,%edx 


int t2 = z+t1;. ecx ban đầu giữ t1, z16(%ebp), ở phần cuối của hướng dẫn ecx sẽ tổ chức t2:

addl 16(%ebp),%ecx 


int t5 = t3 + t4;. t3 đơn giản là x + 4 và thay vì tính và lưu trữ t3, biểu thức của t3 được đặt nội tuyến. Hướng dẫn này cần thiết (x+4) + t4, tương tự như t3 + t4. Nó thêm edx ( t4) và eax ( x) và thêm 4 làm số bù trừ để đạt được kết quả đó.

leal 4(%edx,%eax),%eax 

int rval = t2 * t5; Khá thẳng chuyển tiếp này một; ecx đại diện cho t2eax đại diện cho t5. Giá trị trả lại được chuyển lại cho người gọi qua eax.

imull %ecx,%eax 


Phá hủy các stack frame và khôi phục espebp:

movl %ebp,%esp 
popl %ebp 


Return từ thói quen:

ret 


Từ ví dụ này, bạn có thể thấy rằng resul t là như nhau, nhưng cấu trúc có một chút khác biệt. Rất có thể mã này đã được biên soạn với một số loại tối ưu hóa hoặc ai đó đã tự viết nó như thế để chứng minh một điểm.

Như những người khác đã nói, bạn không thể quay trở lại chính xác nguồn từ việc tháo gỡ. Đó là giải thích của người đọc hội đồng để đưa ra mã C tương đương.


Để giúp với lắp ráp học tập và tìm hiểu tháo gỡ các chương trình C, bạn có thể làm như sau trên Linux:

Compile với thông tin debug (-g), mà sẽ nhúng các nguồn:

gcc -c -g arith.c 

Nếu bạn đang sử dụng máy 64 bit, bạn có thể yêu cầu trình biên dịch tạo một nhị phân 32 bit với cờ -m32 (tôi đã làm như vậy cho ví dụ bên dưới).


Sử dụng objdump để đổ các tập tin đối tượng với nó là nguồn xen kẽ:

objdump -d -S arith.o 

-d = tháo gỡ, -S = nguồn hiển thị. Bạn có thể thêm -M intel-mnemonic để sử dụng cú pháp ASM của Intel nếu bạn thích cú pháp đó hơn cú pháp AT & T mà ví dụ của bạn sử dụng.

Output:

arith.o:  file format elf32-i386 


Disassembly of section .text: 

00000000 <arith>: 
int arith(int x, int y, int z) 
{ 
    0: 55      push %ebp 
    1: 89 e5     mov %esp,%ebp 
    3: 83 ec 20    sub $0x20,%esp 
    int t1 = x+y; 
    6: 8b 45 0c    mov 0xc(%ebp),%eax 
    9: 8b 55 08    mov 0x8(%ebp),%edx 
    c: 01 d0     add %edx,%eax 
    e: 89 45 fc    mov %eax,-0x4(%ebp) 
    int t2 = z+t1; 
    11: 8b 45 fc    mov -0x4(%ebp),%eax 
    14: 8b 55 10    mov 0x10(%ebp),%edx 
    17: 01 d0     add %edx,%eax 
    19: 89 45 f8    mov %eax,-0x8(%ebp) 
    int t3 = x+4; 
    1c: 8b 45 08    mov 0x8(%ebp),%eax 
    1f: 83 c0 04    add $0x4,%eax 
    22: 89 45 f4    mov %eax,-0xc(%ebp) 
    int t4 = y * 48; 
    25: 8b 55 0c    mov 0xc(%ebp),%edx 
    28: 89 d0     mov %edx,%eax 
    2a: 01 c0     add %eax,%eax 
    2c: 01 d0     add %edx,%eax 
    2e: c1 e0 04    shl $0x4,%eax 
    31: 89 45 f0    mov %eax,-0x10(%ebp) 
    int t5 = t3 + t4; 
    34: 8b 45 f0    mov -0x10(%ebp),%eax 
    37: 8b 55 f4    mov -0xc(%ebp),%edx 
    3a: 01 d0     add %edx,%eax 
    3c: 89 45 ec    mov %eax,-0x14(%ebp) 
    int rval = t2 * t5; 
    3f: 8b 45 f8    mov -0x8(%ebp),%eax 
    42: 0f af 45 ec    imul -0x14(%ebp),%eax 
    46: 89 45 e8    mov %eax,-0x18(%ebp) 
    return rval; 
    49: 8b 45 e8    mov -0x18(%ebp),%eax 
} 
    4c: c9      leave 
    4d: c3      ret 

Như bạn thấy, mà không cần tối ưu hóa trình biên dịch tạo ra một nhị phân lớn hơn ví dụ bạn có. Bạn có thể chơi với điều đó và thêm cờ tối ưu hóa trình biên dịch khi biên soạn (ví dụ: -O1, -O2, -O3). Mức độ tối ưu hóa càng cao, thì việc tháo gỡ sẽ càng có vẻ trừu tượng hơn.

Ví dụ, chỉ với mức 1 tối ưu hóa (gcc -c -g -O1 -m32 arith.c1), mã lắp ráp sản xuất là ngắn hơn rất nhiều:

00000000 <arith>: 
int arith(int x, int y, int z) 
{ 
    0: 8b 4c 24 04    mov 0x4(%esp),%ecx 
    4: 8b 54 24 08    mov 0x8(%esp),%edx 
    int t1 = x+y; 
    8: 8d 04 11    lea (%ecx,%edx,1),%eax 
    int t2 = z+t1; 
    b: 03 44 24 0c    add 0xc(%esp),%eax 
    int t3 = x+4; 
    int t4 = y * 48; 
    f: 8d 14 52    lea (%edx,%edx,2),%edx 
    12: c1 e2 04    shl $0x4,%edx 
    int t5 = t3 + t4; 
    15: 8d 54 11 04    lea 0x4(%ecx,%edx,1),%edx 
    int rval = t2 * t5; 
    19: 0f af c2    imul %edx,%eax 
    return rval; 
} 
    1c: c3      ret 
6

Bạn không thể sao chép nguồn gốc, bạn chỉ có thể tạo lại nguồn tương đương.

Trong trường hợp của bạn, phép tính cho t2 có thể xuất hiện ở bất kỳ đâu sau t1 và trước retval.

Nguồn thậm chí có thể đã là một biểu hiện duy nhất:

return (x+y+z) * ((x+4) + (y * 48)); 
1

Decompilation là không hoàn toàn có thể đạt được: có một số mất kiến ​​thức khi đi từ mã nguồn (nơi bình luận & tên đã cho bạn một đầu mối của bản gốc ý định của lập trình viên) cho mã máy nhị phân (nơi các lệnh sẽ được thực hiện bởi bộ xử lý).

5

Khi kỹ thuật đảo ngược, bạn không quan tâm đến dòng mã nguồn ban đầu theo từng dòng, bạn quan tâm đến nó. Một hiệu ứng phụ là bạn thấy mã nào làm, không phải những gì người lập trình dự định làm mã.