2015-12-05 17 views
6

Tôi đã viết một chương trình lắp ráp đơn giản:Tại sao giá trị của EDX bị ghi đè khi thực hiện cuộc gọi đến printf?

section .data 
str_out db "%d ",10,0 
section .text 
extern printf 
extern exit 
global main 
main: 

MOV EDX, ESP 
MOV EAX, EDX 
PUSH EAX 
PUSH str_out 
CALL printf 
SUB ESP, 8 ; cleanup stack 
MOV EAX, EDX 
PUSH EAX 
PUSH str_out 
CALL printf 
SUB ESP, 8 ; cleanup stack 
CALL exit 

Tôi là lắp ráp NASM và GCC để liên kết các tập tin đối tượng đến một thực thi trên Linux.

Về cơ bản, chương trình này trước tiên đặt giá trị của con trỏ ngăn xếp vào thanh ghi EDX, sau đó in nội dung của thanh ghi này hai lần. Tuy nhiên, sau cuộc gọi printf thứ hai, giá trị được in ra cho giá trị xuất chuẩn không khớp với giá trị đầu tiên.

Hành vi này có vẻ lạ. Khi tôi thay thế mọi cách sử dụng EDX trong chương trình này bằng EBX, các số nguyên được xuất ra giống hệt như mong đợi. Tôi chỉ có thể phỏng đoán rằng EDX được ghi đè tại một số điểm trong suốt cuộc gọi hàm printf.

Tại sao lại xảy ra trường hợp này? Và làm thế nào tôi có thể đảm bảo rằng các thanh ghi tôi sử dụng trong tương lai không xung đột với các chức năng lib của C?

+2

Điều đó đã giúp tôi lần đầu tiên nhiều năm trước. Câu trả lời bạn chấp nhận là chính xác nhưng bỏ qua 'ebp' và' esp' làm callee lưu. Hai người đó dường như không nói gì, nhưng bạn có thể lộn xộn về mặt kỹ thuật. Chào mừng bạn đến với hội đồng! – sqykly

+0

@sqykly Cảm ơn bạn. Nó chắc chắn là ít hơn rất nhiều tha thứ hơn so với các ngôn ngữ cấp cao hơn mà tôi đang sử dụng để. Nhưng tôi sẽ không bị đánh bại bởi nó! :) – Jake

+0

Trả lời như nhiều câu hỏi javascript như tôi làm và bạn sẽ bắt đầu tự hỏi về điều đó. – sqykly

Trả lời

11

Theo x86 ABI, EBX, ESI, EDI, và EBP là callee lưu thanh ghi và EAX, ECXEDX là người gọi lưu thanh ghi.

Điều này có nghĩa là các chức năng có thể tự do sử dụng và hủy các giá trị trước đó EAX, ECXEDX. Vì lý do đó, hãy lưu các giá trị của EAX, ECX, EDX trước khi gọi các chức năng nếu bạn không muốn thay đổi giá trị của chúng. Đó là ý nghĩa của "người gọi cứu".

Hoặc tốt hơn, hãy sử dụng các thanh ghi khác cho các giá trị bạn vẫn cần sau một cuộc gọi hàm. push/pop của EBX ở đầu/cuối của một hàm tốt hơn nhiều so với push/pop của EDX bên trong vòng lặp thực hiện cuộc gọi hàm. Khi có thể, hãy sử dụng các thanh ghi được gọi là clobbered cho các thời gian không cần thiết sau cuộc gọi. Giá trị đã có trong bộ nhớ, vì vậy chúng không cần phải viết trước khi được đọc lại, cũng rẻ hơn để tràn.


Kể từ EBX, ESI, EDI, và EBP được callee lưu sổ đăng ký, chức năng phải khôi phục các giá trị với bản gốc cho bất kỳ của những họ chỉnh sửa, trước khi trở lại.

ESP cũng được lưu vào tình trạng thoải mái, nhưng bạn không thể làm hỏng điều này trừ khi bạn sao chép địa chỉ trả lại ở đâu đó. Gọi/rút lại không phù hợp là khủng khiếp đối với hiệu suất vì các CPU hiện đại sử dụng bộ dự đoán địa chỉ trả về.

+2

'EBP' cũng tiết kiệm được! –

+0

Nó không * mà * khó. 'ret 8' từ một hàm không có tham số messes up' esp'. Hình ảnh bất kỳ loại tối ưu hóa cuộc gọi đuôi đã đi sai. – sqykly

+0

Hoặc! Cdeclappappied hoặc stdcall. – sqykly

5

ABI cho nền tảng đích (ví dụ: 32bit x86 Linux) xác định sổ đăng ký nào có thể được sử dụng bởi các hàm mà không lưu. (tức là, nếu bạn muốn chúng được bảo tồn qua một cuộc gọi, bạn phải tự mình thực hiện).

Các liên kết đến tài liệu ABI cho Windows và không cửa sổ, 32 và 64bit, tại https://stackoverflow.com/tags/x86/info

Có một số đăng ký mà không được bảo quản trên các cuộc gọi (có sẵn như là thanh ghi đầu) có nghĩa là chức năng có thể nhỏ hơn. Các chức năng đơn giản thường có thể tránh thực hiện bất kỳ lưu trữ/khôi phục nào push/pop. Điều này cắt giảm số lượng hướng dẫn, dẫn đến mã nhanh hơn.

Điều quan trọng là phải có một số của mỗi: phải tràn tất cả trạng thái vào bộ nhớ qua các cuộc gọi sẽ làm nổi bật mã của các chức năng không phải lá, và làm chậm những điều đặc biệt. trong trường hợp hàm được gọi không chạm vào tất cả các thanh ghi.

+0

Đoạn cuối cùng nghe có vẻ buồn cười. Nếu bạn phải lưu tất cả trạng thái vào bộ nhớ, các chức năng của lá chính xác là những gì nó sẽ sưng lên. Các chức năng không lá về cơ bản làm sưng lên theo một trong hai cách, vì chúng vừa là người gọi vừa là một người bình thường. –

+0

@DanielStevens: đoạn cuối cùng là nói về trường hợp tất cả các thanh ghi được ghi đè, giống như các ký tự xmm nằm trong SysV 64bit ABI. Chức năng lá không phải lưu bất cứ thứ gì. Ngoài ra: các chức năng không phải lá thường có đủ thanh ghi callee để lưu giữ một vài phần quan trọng của trạng thái trong regs, và chủ yếu sử dụng regs người gọi lưu làm không gian đầu để tính toán các tham số gọi hàm. Bạn chỉ cần lưu/khôi phục lại một reg nếu bạn vẫn cần nó sau khi gọi hàm. Thông thường bạn cần một cặp vợ chồng nghĩ, giống như một bộ đếm vòng lặp và một hoặc hai con trỏ, nhưng có thể tải lại các thứ khác. –

+0

Nhưng bạn đang nói về việc tràn tất cả trạng thái vào bộ nhớ, không để cho nó bị che khuất. –

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