Tôi không có kinh nghiệm lắp ráp, nhưng đây là những gì tôi đã và đang làm. Nếu thiếu bất kỳ khía cạnh cơ bản nào khi truyền tham số và gọi hàm thông qua con trỏ trong hợp ngữ, thì tôi muốn có đầu vào.
Ví dụ, tôi muốn biết liệu tôi có nên hoàn nguyênngoại hối
,edx
,esi
,biên tập
,
. Tôi đọc được rằng chúng là những thanh ghi có mục đích chung nhưng tôi không thể tìm thấy chúng. Tôi có nên làm gì để dọn dẹp sau cuộc gọi không?
Đây là mã hiện tại của tôi và nó thực sự hoạt động:
#include "stdio.h"
void foo(int a, int b, int c, int d)
{
printf("giá trị = %d và %d và %d và %d\r\n", a, b, c, d);
}
int main()
{
int a=3,b=6,c=9,d=12;
__asm__(
"di chuyển %3, %%ecx;"
"di chuyển %2, %%edx;"
"di chuyển %1, %%esi;"
"di chuyển %0, %%edi;"
"gọi %4;"
:
: "g"(a), "g"(b), "g"(c), "g"(d), "a"(foo)
);
}
Câu hỏi ban đầu làCuộc gọi chức năng lắp ráp này có an toàn/hoàn chỉnh không?
. Câu trả lời là: không. Mặc dù nó có vẻ hoạt động trong ví dụ đơn giản này (đặc biệt khi tính năng tối ưu hóa bị vô hiệu hóa), nhưng bạn đang vi phạm các quy tắc (những quy tắc khó tìm) mà cuối cùng sẽ gây ra lỗi.
Tôi muốn giải quyết câu hỏi tiếp theo (rõ ràng) về cách đảm bảo an toàn, nhưng tôi thực sự không thể làm điều đó nếu không có phản hồi của nhà điều hành về mục đích thực tế.
Vì vậy, tôi sẽ cố gắng hết sức với những gì chúng tôi có và cố gắng mô tả những điều khiến nó không an toàn cũng như một số điều bạn có thể làm để giải quyết vấn đề đó.
Hãy bắt đầu bằng cách đơn giản hóa asm:
__asm__(
"di chuyển %0, %%edi;"
:
: "g"(a)
);
Ngay cả với câu lệnh duy nhất này, mã này đã không an toàn. Tại sao? Bởi vì chúng ta thay đổi giá trị của một thanh ghi (edi) mà không cho trình biên dịch biết.
Làm sao trình biên dịch không biết bạn đang hỏi? Rốt cuộc thì nó ở đó! Câu trả lời đến từ
tài liệu gccDòng này trong:
GCC không tự phân tích các hướng dẫn của trình biên dịch mã, cũng như không
Biết ý nghĩa của chúng và thậm chí liệu chúng có phải là đầu vào hợp lệ của trình biên dịch mã hay không.
Trong trường hợp này, làm cách nào để bạn cho GCC biết chuyện gì đang xảy ra? Câu trả lời nằm ở việc sử dụng các ràng buộc (cái đứng sau dấu hai chấm) để mô tả tác động của asm.
Có lẽ cách dễ nhất để sửa mã này là như sau:
__asm__(
"di chuyển %0, %%edi;"
:
: "g"(a)
: biên tập
);
Điều này sẽ thêm edi vào
danh sách người làm hỏng việcở giữa. Nói tóm lại, điều này cho GCC biết rằng giá trị của EDI sẽ được mã thay đổi và GCC không nên cho rằng bất kỳ giá trị cụ thể nào sẽ xuất hiện khi ASM thoát.
Bây giờ, mặc dù đây là cách đơn giản nhất nhưng nó có thể không nhất thiết là cách tốt nhất. Hãy xem xét đoạn mã sau:
__asm__(
""
:
: "D"(a)
);
nó sử dụng
hạn chế máyYêu cầu gcc thay đổi biến
Một
Giá trị được đặt trong thanh ghi edi. Làm điều này, gcc sẽ tải sổ đăng ký cho bạn khi nó "thuận tiện", có thể bằng cách luôn đặt
Một
Lưu trong edi.
Mã này có một cảnh báo (quan trọng): bằng cách đặt tham số sau dấu hai chấm thứ hai, chúng tôi khai báo nó là đầu vào. Các tham số đầu vào cần phải ở dạng chỉ đọc (tức là chúng phải có cùng giá trị khi thoát ASM).
Trong trường hợp của bạn,
gọi
có nghĩa là chúng tôi không đảm bảo rằng edi sẽ không bị thay đổi, vì vậy điều này không hiệu quả lắm. Có một số cách để giải quyết vấn đề này. Cách đơn giản nhất là di chuyển ràng buộc lên sau dấu hai chấm đầu tiên, biến nó thành đầu ra và chỉ định
"+D"
để chỉ ra rằng giá trị là đọc + ghi. Tuy nhiên, sau asm,
Một
Nội dung của sẽ gần như không được xác định (printf có thể đặt nó thành bất kỳ giá trị nào). nếu bị phá hủy
Một
không thể chấp nhận được, luôn có những tình huống như thế này:
int rác;
__asm__ dễ bay hơi (
""
: "=D" (rác)
: "0"(a)
);
Điều này cho gcc biết rằng khi bắt đầu asm, nó sẽ thay đổi giá trị biến thành
Một
Đặt nó ở cùng vị trí với ràng buộc đầu ra 0 (edi). Nó cũng nói rằng ở đầu ra, edi sẽ không còn
Một
, sẽ chứa các biến
rác
.
EDIT: Vì biến "rác" thực sự không được sử dụng nên chúng ta cần thêm
bay hơi
Vòng loại. Dễ bay hơi là ẩn mà không có bất kỳ tham số đầu ra nào.
Một điểm khác trên dòng đó: kết thúc bằng dấu chấm phẩy. Điều này là hợp pháp và sẽ hoạt động như mong đợi. Tuy nhiên, nếu bạn muốn sử dụng
-S
tùy chọn dòng lệnh để xem chính xác mã nào đã được tạo (nếu bạn muốn sử dụng tốt asm nội tuyến, bạn sẽ thấy rằng điều này tạo ra mã khó đọc. Tôi khuyên bạn nên sử dụng
\n\t
Thay thế dấu chấm phẩy.
Tất cả những điều này và chúng tôi vẫn đang ở tuyến đầu. . .
Rõ ràng điều tương tự cũng áp dụng cho hai cái còn lại
di chuyển
tuyên bố.
Điều này dẫn đến
gọi
tuyên bố.
Michael và tôi đều liệt kê một số lý do khiến việc gọi asm nội tuyến lại khó khăn.
Xử lý tất cả các thanh ghi có thể bị hỏng do ABI của lệnh gọi hàm.
Xử lý các khu vực màu đỏ.
Xử lý căn chỉnh.
Sốc trí nhớ.
Nếu mục tiêu ở đây là "học tập" thì hãy thử nó. Nhưng tôi không biết liệu mình có cảm thấy thoải mái khi làm điều này trong mã sản xuất hay không. Ngay cả khi nó có vẻ hiệu quả, tôi cũng không tự tin rằng mình sẽ không bỏ lỡ một trường hợp kỳ lạ nào đó. Điều này phù hợp với những gì tôi thường quan tâm
hoàn toàn sử dụng asm nội tuyến无关。
Tôi biết, đó là rất nhiều thông tin. như gcc
asm
Có thể có nhiều mệnh lệnh hơn bạn mong đợi nhưng bạn đã chọn một điểm khởi đầu đầy thử thách.
Nếu bạn chưa làm như vậy, hãy dành thời gian xem gcc
Giao diện ngôn ngữ hộiTất cả các tài liệu ở định dạng . Có rất nhiều thông tin hữu ích ở đây và một số ví dụ giải thích cách thức hoạt động của nó.
Tôi là một lập trình viên xuất sắc, rất giỏi!