2010-08-12 27 views
8

Nếu bạn muốn gọi một C/C++ chức năng từ lắp ráp nội tuyến, bạn có thể làm một cái gì đó như thế này:trực tiếp gọi điện bằng GCC của lắp ráp nội tuyến

void callee() {} 
void caller() 
{ 
    asm("call *%0" : : "r"(callee)); 
} 

GCC sau đó sẽ phát ra mã mà trông như thế này:

movl $callee, %eax 
call *%eax 

Điều này có thể có vấn đề vì cuộc gọi gián tiếp sẽ phá hủy đường dẫn trên các CPU cũ hơn.

Vì địa chỉ callee cuối cùng là một hằng số, người ta có thể tưởng tượng rằng nó sẽ có thể sử dụng ràng buộc i. Trích dẫn từ GCC trực tuyến docs:

`i'

An immediate integer operand (one with constant value) is allowed. This includes symbolic constants whose values will be known only at assembly time or later.

Nếu tôi cố gắng sử dụng nó như thế này:

asm("call %0" : : "i"(callee)); 

tôi nhận được lỗi sau từ lắp ráp:

Error: suffix or operands invalid for `call'

Điều này là do GCC phát ra mã

call $callee 

Thay vì

call callee 

Vì vậy, câu hỏi của tôi là liệu nó có thể làm cho sản lượng GCC đúng call.

+1

Bạn có chắc chắn cuộc gọi gián tiếp phá hủy đường dẫn không? Bạn đã chuẩn bị chưa? Sự hiểu biết của tôi là vào những ngày cũ trên x86 (trước i686), các cuộc gọi gián tiếp rất tệ (tôi nhớ chúng là chậm hơn 10-100 lần so với K6 của tôi), nhưng ngày nay cpus thông minh hơn và có thể xử lý chúng tốt . Vì vậy, làm một số thử nghiệm trước khi bạn nhảy đến kết luận! –

+0

@R ..: Bạn nói đúng: nếu tôi đánh giá điều này trên một CPU thực sự, nó không tạo ra bất kỳ sự khác biệt nào. Tôi đang chạy mã của tôi trong qemu, tuy nhiên, và nó dường như tạo sự khác biệt ở đó (khoảng 20% ​​chu kỳ/cuộc gọi). – Job

+0

Sau đó, tôi sẽ chỉ gắn bó với cách bạn đang thực hiện nó, với cuộc gọi gián tiếp. Điều này sẽ cho phép gcc tạo mã đúng cho các thư viện/thực thi PIC/PIE mà không cần phải chèn các hacks đặc biệt để xử lý những thứ này. –

Trả lời

10

tôi có answer từ mailing list của GCC:

asm("call %P0" : : "i"(callee)); 

Bây giờ tôi chỉ cần tìm hiểu những gì %P0 thực sự có nghĩa là vì nó có vẻ là một tính năng không có giấy tờ ...

Sửa: Sau khi nhìn vào mã nguồn GCC, nó không chính xác rõ ràng mã số P trước một phương tiện ràng buộc. Nhưng, trong số những thứ khác, nó ngăn GCC đặt một $ trước các giá trị không đổi. Đó là chính xác những gì tôi cần trong trường hợp này.

+0

Xem thêm https://stackoverflow.com/questions/37639993/is-this-assembly-function-call-safe-complete cho ** các mối nguy hiểm khi sử dụng 'cuộc gọi' từ nội tuyến asm **. Về cơ bản nó không thể làm một cách đáng tin cậy cho hệ thống x86-64 V (Linux), bởi vì bạn không thể nói với trình biên dịch mà bạn muốn che khuất vùng màu đỏ. Nó thường là một ý tưởng tồi. Xem https://stackoverflow.com/questions/7984209/calling-a-function-in-gcc-inline-assembly. –

0

Làm thế nào về.

asm volatile ("call $callee"); 

vì bạn biết tên của biểu tượng, dù sao đi nữa.

Chỉnh sửa: Điểm tôi muốn thực hiện là bạn không phải thực hiện các quy ước của gcc cho các đối số asm ở đây, nhưng bạn chỉ cần thực hiện xử lý văn bản để thực hiện công việc.

+0

Vâng, đó sẽ là phương sách cuối cùng của tôi (nó phải là 'asm (" gọi callee ");', mặc dù). Vấn đề là bạn sẽ cần tên biểu tượng bị cắt xén. Đây không phải là một vấn đề lớn trong C nhưng PITA trong C++. – Job

+0

Điều này sẽ là 'asm (" gọi callee ");', nhưng trong C++, điều này sẽ giống như 'asm (" call _ZL4callv "); ' –

+4

Điều này không có tác dụng, vì nhiều lý do. Một, các hệ điều hành khác nhau (trên cùng một phần cứng) có các quy ước khác nhau về cách các tên biểu tượng được ánh xạ (sự kỳ quặc phổ biến nhất là thêm một dấu gạch dưới). Và hai, trừ khi gcc là bằng cách nào đó nói rằng asm sử dụng chức năng, nó có thể rất tốt không bao giờ bỏ qua một phiên bản độc lập có thể gọi của chức năng. Ví dụ, nó có thể in nó ở mọi nơi khác nó được sử dụng, hoặc có thể bỏ qua nó hoàn toàn nếu nó không bao giờ được gọi từ mã C. Ba, có thể có vấn đề với mã PIC và PIE ... –

0

Bí quyết là nối chuỗi ký tự. Trước khi GCC bắt đầu cố gắng lấy bất kỳ ý nghĩa thực sự nào từ mã của bạn, nó sẽ nối chuỗi chữ liền kề, vì vậy, mặc dù chuỗi lắp ráp không giống với các chuỗi khác mà bạn sử dụng trong chương trình của bạn, chúng sẽ được ghép nối nếu bạn làm:

#define ASM_CALL(X) asm("\t call " X "\n") 


int main(void) { 
    ASM_CALL("my_function"); 
    return 0; 
} 

Vì bạn đang sử dụng GCC bạn cũng có thể làm

#define ASM_CALL(X) asm("\t call " #X "\n") 

int main(void) { 
    ASM_CALL(my_function); 
    return 0; 
} 

Nếu bạn chưa biết, bạn nên lưu ý rằng gọi thứ từ lắp ráp nội tuyến là rất khó khăn. Khi trình biên dịch tạo ra các cuộc gọi riêng của mình đến các chức năng khác, nó bao gồm mã để thiết lập và khôi phục mọi thứ trước và sau cuộc gọi. Nó không biết rằng nó nên được thực hiện bất kỳ điều này cho cuộc gọi của bạn, mặc dù. Bạn sẽ phải bao gồm chính bản thân mình (rất phức tạp để có được quyền và có thể phá vỡ bằng cách nâng cấp trình biên dịch hoặc cờ biên dịch) hoặc đảm bảo rằng hàm của bạn được viết theo cách nó dường như không thay đổi bất kỳ thanh ghi hoặc điều kiện nào của stack (hoặc biến trên nó).

chỉnh sửa điều này sẽ chỉ hoạt động đối với tên hàm C - không phải C++ khi chúng bị xáo trộn.

+1

Không, điều này không hoạt động chút nào.Đầu tiên, '$' hoàn toàn sai đối với cuộc gọi, thứ hai anh ta không muốn "my_function" trong chuỗi asm, nhưng ** tên bị xáo trộn mà C++ tạo **. –

+0

Tôi không thấy rằng điều này cũng dành cho C++. Trong trường hợp đó, có khả năng điều này sẽ không hoạt động nếu tên hàm bị quá tải mà không có nhiều vòng lặp. Tôi chỉ cần sao chép "$" từ một bài đăng khác vì tôi không nhớ tay cú pháp x86 asm. Đang sửa lỗi đó ... – nategoose

0

Nếu bạn đang tạo mã 32-bit (ví dụ -m32 gcc tùy chọn), các inline asm sau phát ra một cuộc gọi trực tiếp:

asm ("call %0" :: "m" (callee)); 
1

lẽ tôi đang thiếu một cái gì đó ở đây, nhưng

extern "C" void callee(void) 
{ 

} 

void caller(void) 
{ 
    asm("call callee\n"); 
} 

sẽ hoạt động tốt. Bạn cần extern "C" để tên sẽ không được trang trí dựa trên C++ đặt tên quy tắc mangling.

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