2011-12-24 26 views
13

Tôi đang cố gắng hiểu rõ mã thực thi mà GCC (4.4.3) đang tạo cho máy x86_64 chạy trên Ubuntu Linux. Đặc biệt, tôi không hiểu cách mã theo dõi các khung ngăn xếp. Trong những ngày cũ, trong mã 32-bit, tôi đã quen với việc nhìn thấy điều này "mở đầu" trong chỉ là về tất cả các chức năng:x86_64 quy ước gọi và ngăn xếp khung

push %ebp 
movl %esp, %ebp 

Sau đó, vào cuối của hàm, sẽ đến một "lời bạt," hoặc

sub $xx, %esp # Where xx is a number based on GCC's accounting. 
pop %ebp 
ret 

hoặc đơn giản là

leave 
ret 

mà hoàn thành được điều tương tự:

  • Đặt Con trỏ ngăn xếp ở trên cùng của khung hiện tại, ngay bên dưới địa chỉ trả lại
  • Khôi phục giá trị Con trỏ khung cũ.

Trong mã 64 bit, như tôi thấy thông qua việc tháo gỡ objdump, nhiều chức năng không tuân theo quy ước này - chúng không đẩy% rbp và sau đó lưu% rsp thành% rbp, Trình gỡ lỗi như thế nào GDB xây dựng một backtrace?

Mục tiêu thực sự của tôi ở đây là cố gắng tìm ra một địa chỉ hợp lý để xem xét ở vị trí trên cùng (địa chỉ cao nhất) của ngăn xếp người dùng khi thực thi đạt đến chức năng tùy ý trong chương trình, nơi có thể là Stack Pointer đã di chuyển xuống. Ví dụ, đối với "top", địa chỉ ban đầu của argv sẽ là lý tưởng - nhưng tôi không có quyền truy cập vào nó từ một hàm tùy ý mà các cuộc gọi chính. Lúc đầu tôi nghĩ rằng tôi có thể sử dụng phương pháp backtrace cũ: theo đuổi giá trị Frame Pointer đã lưu cho đến khi giá trị được lưu là 0 - sau đó, giá trị tiếp theo sau đó có thể được tính là giá trị thực tế cao nhất. (Điều này không giống như nhận địa chỉ của argv, nhưng nó sẽ làm - giả sử, để tìm ra giá trị Stack Pointer tại _start hoặc bất kỳ lệnh gọi _start nào [ví dụ, __libc_start_main].) Bây giờ, tôi không biết làm thế nào để lấy địa chỉ tương đương trong mã 64 bit.

Cảm ơn.

+0

Thật vậy. Và nó không chỉ với '-fomit-frame-pointer'. –

+1

Bạn đã thử -fno-omit-frame-pointer chưa? Bạn có thể biên dịch mã khác với lá cờ đó không? –

+0

Mã nguồn để 'libunwind' có thể hữu ích. – Nemo

Trả lời

5

Tôi nghĩ sự khác biệt là bỏ qua con trỏ khung chỉ đơn giản là được khuyến khích trong amd64. Một chú thích trên trang 16 của abi nói

Việc sử dụng thông thường của% RBP như một con trỏ khung cho khung ngăn xếp có thể tránh được bằng cách sử dụng % RSP (stack pointer) để chỉ số vào stack frame. Kỹ thuật này tiết kiệm hai hướng dẫn trong phần mở đầu và phần kết và tạo thêm một thanh ghi đa năng bổ sung (% rbp) có sẵn.

Tôi không biết GDB làm gì. Tôi giả sử rằng khi được biên dịch với -g, các đối tượng có thông tin gỡ lỗi ma thuật cho phép GDB tái tạo lại những gì nó cần. Tôi không nghĩ rằng tôi đã thử GDB trên một máy 64-bit mà không có thông tin gỡ lỗi.

+0

My kinh nghiệm với x86-64 đã chỉ ra rằng trình gỡ rối sử dụng thông tin bổ sung để biết kích thước khung ngăn xếp, giúp tiết kiệm các hướng dẫn nhưng làm cho việc gỡ lỗi và giải quyết một cơn đau. – Brian

+0

Đúng, như tôi nghi ngờ. Và liệu tất cả đều bork lên khi thực thi được biên dịch mà không cần thông tin gỡ lỗi? –

+0

Cảm ơn bạn. Đề xuất đó trong ABI không giải thích được những gì đang diễn ra - nhưng nó vẫn khiến tôi tự hỏi làm cách nào để giải quyết vấn đề của mình. Tôi cần phải nhận được - gần như nói - giá trị của Stack Pointer khi thực hiện nhập chính, từ một chức năng tùy ý mà đến sau khi chính trong đồ thị cuộc gọi. Giá trị có thể cao hơn giá trị thực tế của phần trên cùng của khung ngăn xếp chính, miễn là nó nằm trong ngăn xếp của quá trình, nhưng gần hơn với phần trên cùng của khung ngăn xếp chính, thì tốt hơn. –

1

Nếu địa chỉ của argv là những gì bạn muốn, tại sao không chỉ lưu một con trỏ vào nó trong chính?
Cố gắng để thư giãn ngăn xếp sẽ là rất không thể di chuyển, ngay cả khi bạn làm cho nó hoạt động.
Thậm chí nếu bạn quản lý để quay trở lại ngăn xếp, nó không phải là rõ ràng rằng con trỏ khung của hàm đầu tiên sẽ là NULL. Hàm đầu tiên trên ngăn xếp không trả lại, nhưng gọi một cuộc gọi hệ thống để thoát, và do đó con trỏ khung của nó không bao giờ được sử dụng.Không có lý do chính đáng tại sao nó sẽ được khởi tạo thành NULL.

+0

Cảm ơn. Than ôi, không, tôi không thể lưu con trỏ vào chính. Tôi đang viết một thư viện cấp người dùng để liên kết với mã tùy ý, vì vậy tôi không thể chạm vào mã ban đầu (ngoại trừ thêm #include) - hoặc muốn tránh làm như vậy nếu có thể. Về điểm thứ hai của bạn, tôi có ấn tượng rằng các hạt nhân như Linux thực hiện theo quy ước thiết lập Frame Pointer thành NULL trước khi chuyển điều khiển đến một quy trình người dùng, chính xác cho mục đích này. Nhưng có lẽ đó chỉ là một quy ước cũ mà không phải tất cả các hệ thống đều theo sau nữa. –

1

Giả sử rằng tôi đang liên kết với glibc (mà tôi đang làm), có vẻ như nếu tôi có thể giải quyết vấn đề này cho các mục đích thực tế với glibc toàn cầu biểu tượng __libc_stack_end:

extern void * __libc_stack_end; 

void myfunction(void) { 
    /* ... */ 
    off_t stack_hi = (off_t)__libc_stack_end; 
    /* ... */ 
} 
3

GDB sử dụng CFI lùn cho thư giãn. Đối với các tệp nhị phân chưa được biên dịch được biên dịch bằng -g, phần này sẽ nằm trong phần .debug_info. Đối với các tệp nhị phân x86-64, có thông tin bung ra trong phần .eh_frame. Điều này được định nghĩa trong phần x86-64 ABI, phần 3.7, trang 56. Tự xử lý thông tin này là khá khó, vì việc phân tích cú pháp DWARF rất có liên quan, nhưng tôi tin rằng libunwind có hỗ trợ cho nó.

+0

Tôi khá chắc chắn rằng nó luôn luôn trong phần '.eh_frame', đó là * tại sao * nó vẫn còn đó sau khi tước. Cách bạn mô tả nó, 'strip' sẽ phải tìm thông tin đó trong' .debug_info' và sao chép nó vào '.eh_frame', và thư giãn sẽ phải kiểm tra cả hai vị trí ... –

+0

' .debug_info' chứa * thông tin thêm * về các biến cục bộ bên trong các khung ngăn xếp, nhưng '.eh_frame' luôn chứa đầy đủ thông tin để giải phóng ngăn xếp. (nghĩa là kích thước của mỗi khung ngăn xếp và nơi lưu sổ đăng ký được lưu trữ bằng callee, nhưng không lưu được biến nào được lưu trữ ở đâu.) –

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