2015-12-11 16 views
8

Tôi đang thực hiện triển khai tùy chỉnh setjmp/longjmp tùy chỉnh cho hệ thống x86-64 giúp lưu toàn bộ ngữ cảnh của CPU (cụ thể là tất cả xmm, fpu stack, v.v.) callee-lưu sổ đăng ký). Điều này được viết trực tiếp trong hội đồng.x86_64: buộc gcc chuyển đối số trên ngăn xếp

Mã hoạt động tốt trong các ví dụ tối thiểu (khi gọi trực tiếp từ nguồn lắp ráp). Vấn đề phát sinh khi sử dụng nó với mã C, do các thông số cách được truyền cho các hàm homebrew setjmp/longjmp. Trong thực tế, SysV ABI cho các hệ thống x64_64 ra lệnh rằng các đối số nên được truyền qua các thanh ghi (nếu chúng lớn nhất là 6). Chữ ký của các chức năng của tôi là:

long long set_jmp(exec_context_t *env);
__attribute__ ((__noreturn__)) void long_jmp(exec_context_t *env, long long val);

Tất nhiên, điều này không thể làm việc như vậy. Thực tế, khi tôi nhập set_jmp, rdirsi đã bị ghi đè để giữ con trỏ đến envval. Điều tương tự cũng áp dụng cho long_jmp đối với rdi.

Có cách nào để buộc GCC, ví dụ: bằng cách dựa vào một số thuộc tính, để buộc đối số đi qua ngăn xếp? Điều này sẽ được thanh lịch hơn nhiều so với gói set_jmplong_jmp với một số xác định mà thủ công đẩy các thanh ghi clobbered trên ngăn xếp, để lấy chúng sau này.

+1

Điều này sẽ phá vỡ PCS/ABI. Bạn đang tiến gần từ phía sai. Mã lắp ráp của bạn phải tuân theo ABI. Tốt nhất là sử dụng các hàm C với trình lắp ráp nội tuyến trực tiếp hoặc làm trình bao bọc cho mã thực của bạn. Bằng cách đó bạn chỉ có thể specifcy mà đăng ký/bộ nhớ clobbers mã của bạn và để lại tiết kiệm/phục hồi để gcc. – Olaf

+2

'setjmp' không cần lưu' rdi' và 'rsi' — như bạn nói, chúng được ghi đè bên trong' setjmp' vậy tại sao chúng nên được lưu? Trên thực tế, chỉ các thanh ghi được đánh dấu là "được lưu trữ bằng callee" (tức là rbp, ebx, r12, r13, r14, r15 và rsp nhiên) phải được giữ nguyên. – fuz

+0

Tôi đồng ý rằng tôi không tôn trọng ABI, nhưng có một lý do cho điều đó. Tất cả mọi thứ hoạt động tốt với 'setjmp' và' longjmp' bởi vì những gì không được lưu bởi 'setjmp' thực sự được lưu bởi người gọi, trong trường hợp nó là cần thiết, vì chúng là người gọi lưu các thanh ghi. Trong ứng dụng của tôi, tôi có một tương tác với hạt nhân Linux, khi một ngắt được cho, trả về điều khiển cho một phần khác của mã mức người dùng, trong đó, lần lượt, gọi 'setjmp'. Bằng cách xây dựng này, mã thực thi ban đầu không biết rằng 'setjmp' đang được gọi, và do đó người gọi-lưu sổ đăng ký nên được lưu là tốt. – ilpelle

Trả lời

2

Bạn có thể tránh ghi đè lên thanh ghi bằng cách gọi hàm bằng cách sử dụng lắp ráp nội tuyến.

#include <stdio.h> 

static void foo(void) 
{ 
     int i; 
     asm volatile ("mov 16(%%rbp), %0" : "=g" (i)); 
     printf("%d\n", i); 
} 

#define foo(x) ({ int _i = (x); \ 
     asm ("push %0\ncall %P1\nadd $8, %%rsp\n" : : "g"(_i), "i"(foo)); }) 

int main(int argc, char *argv[]) 
{ 
     foo(argc-1); 
     return 0; 
} 

Ở đây, một số nguyên được đẩy lên ngăn xếp và hàm foo được gọi. foo làm cho giá trị có sẵn trong biến cục bộ của nó i. Sau khi trở về, con trỏ ngăn xếp được điều chỉnh trở lại giá trị ban đầu của nó.

+1

Điều này tất nhiên sẽ phá vỡ khủng khiếp nếu hàm gọi là 'main()' không sử dụng '% rbp' làm con trỏ khung, nhưng thay vì sử dụng nó như một thanh ghi nguyên nguyên, hoặc không sử dụng nó để tránh chi phí push/pop. Sau đó là những gì thực sự xảy ra cho mã này nếu bạn biên dịch với '-O2'. –

+0

@NateEldredge: chỉ có 'foo' được thiết kế tồi, nhưng nó chỉ là một trình giữ chỗ cho ví dụ của anh ta. Các macro để tạo ra một tùy chỉnh-ABI 'gọi' có vẻ tốt. OP sẽ sử dụng một thực thi 'setjmp' tùy chỉnh được viết bằng tay trong ASM mà sẽ không tạo ra bất kỳ giả định nào. Nó sẽ bắt đầu bằng cách đẩy một số sổ đăng ký để có được một số regs đầu, sau đó lưu tất cả các trạng thái của người gọi. IDK nếu 'xsave' hoạt động tốt từ không gian người dùng. –

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