Cố gắng hiểu tùy chọn gcc -fomit-frame-pointer


79

Tôi đã yêu cầu Google cung cấp cho tôi ý nghĩa của gcctùy chọn -fomit-frame-pointer, điều này sẽ chuyển hướng tôi đến câu lệnh bên dưới.

-fomit-frame-pointer

Đừng giữ con trỏ khung trong sổ đăng ký cho các chức năng không cần. Điều này tránh được các hướng dẫn lưu, thiết lập và khôi phục con trỏ khung; nó cũng làm cho một thanh ghi bổ sung có sẵn trong nhiều chức năng. Nó cũng làm cho việc gỡ lỗi không thể trên một số máy.

Theo hiểu biết của tôi về mỗi hàm, một bản ghi kích hoạt sẽ được tạo trong ngăn xếp của bộ nhớ tiến trình để giữ tất cả các biến cục bộ và một số thông tin khác. Tôi hy vọng con trỏ khung này có nghĩa là địa chỉ của bản ghi kích hoạt của một hàm.

Trong trường hợp này, loại chức năng nào mà nó không cần giữ con trỏ khung trong một thanh ghi? Nếu tôi nhận được thông tin này, tôi sẽ cố gắng thiết kế hàm mới dựa trên đó (nếu có thể) vì nếu con trỏ khung không được giữ trong các thanh ghi, một số lệnh sẽ bị bỏ qua trong hệ nhị phân. Điều này thực sự sẽ cải thiện hiệu suất đáng kể trong một ứng dụng có nhiều chức năng.


4
Chỉ cần gỡ lỗi chỉ một kết xuất lỗi của mã được biên dịch với tùy chọn này sẽ đủ để giúp bạn loại bỏ tùy chọn này khỏi trang điểm của mình. Nó không xóa bất kỳ hướng dẫn nào btw, nó chỉ cung cấp cho trình tối ưu hóa thêm một thanh ghi nữa để làm việc để lưu trữ.
Hans Passant

1
@HansPassant Trên thực tế, nó khá hữu ích cho các bản dựng phát hành. Có hai mục tiêu trong Makefile - ReleaseDebugthực sự rất hữu ích, hãy lấy tùy chọn này làm ví dụ.
Kotauskas

2
@VladislavToncharov Tôi đoán bạn chưa bao giờ cần gỡ lỗi crash dump từ một khách hàng đang chạy Release-build của bạn ?
Andreas Magnusson

Câu trả lời:


58

Hầu hết các chức năng nhỏ hơn không cần con trỏ khung - các chức năng lớn hơn CÓ THỂ cần một con trỏ.

Nó thực sự về việc trình biên dịch quản lý tốt như thế nào để theo dõi cách ngăn xếp được sử dụng và vị trí của các thứ trên ngăn xếp (các biến cục bộ, các đối số được truyền cho hàm hiện tại và các đối số đang được chuẩn bị cho một hàm sắp được gọi). Tôi không nghĩ rằng việc mô tả các chức năng cần hoặc không cần con trỏ khung là dễ dàng (về mặt kỹ thuật, KHÔNG chức năng nào CÓ để có con trỏ khung - đó là trường hợp "nếu trình biên dịch thấy cần thiết để giảm độ phức tạp của mã khác ").

Tôi không nghĩ rằng bạn nên "cố gắng làm cho các hàm không có con trỏ khung" như một phần của chiến lược viết mã của bạn - như tôi đã nói, các hàm đơn giản không cần chúng, vì vậy hãy sử dụng -fomit-frame-pointervà bạn sẽ nhận được thêm một thanh ghi nữa. cho bộ cấp phát thanh ghi, và lưu 1-3 hướng dẫn vào / ra các chức năng. Nếu hàm của bạn cần con trỏ khung, đó là do trình biên dịch quyết định đó là một lựa chọn tốt hơn là không sử dụng con trỏ khung. Mục tiêu không phải là có các chức năng mà không có con trỏ khung, mục tiêu là có mã hoạt động chính xác và nhanh chóng.

Lưu ý rằng "không có con trỏ khung" sẽ mang lại hiệu suất tốt hơn, nhưng đó không phải là một số viên đạn ma thuật mang lại những cải tiến to lớn - đặc biệt là không có trên x86-64, vốn đã có 16 thanh ghi để bắt đầu. Trên x86 32-bit, vì nó chỉ có 8 thanh ghi, một trong số đó là con trỏ ngăn xếp và việc chiếm một cái khác làm con trỏ khung có nghĩa là 25% không gian đăng ký được sử dụng. Thay đổi điều đó thành 12,5% là một sự cải thiện. Tất nhiên, biên dịch cho 64-bit cũng sẽ giúp ích rất nhiều.


24
Thông thường, trình biên dịch có thể tự theo dõi độ sâu ngăn xếp và không cần con trỏ khung. Ngoại lệ là nếu hàm sử dụng allocamà di chuyển con trỏ ngăn xếp theo một số lượng thay đổi. Việc bỏ sót con trỏ khung làm cho việc gỡ lỗi khó hơn đáng kể. Các biến cục bộ khó xác định hơn và dấu vết ngăn xếp khó tạo lại hơn nhiều nếu không có con trỏ khung để trợ giúp. Ngoài ra, việc truy cập các tham số có thể đắt hơn vì chúng ở xa đầu ngăn xếp và có thể yêu cầu các chế độ địa chỉ đắt hơn.
Raymond Chen

3
Có, vì vậy, giả sử chúng ta không sử dụng alloca[ai làm? - Tôi chắc chắn 99% là tôi chưa bao giờ viết mã sử dụng alloca] hoặc variable size local arrays[là dạng hiện đại của alloca], thì trình biên dịch CÓ THỂ vẫn quyết định rằng sử dụng con trỏ khung là một lựa chọn tốt hơn - bởi vì các trình biên dịch được viết để không tuân theo một cách mù quáng tùy chọn đưa ra, nhưng cung cấp cho bạn những lựa chọn tốt nhất.
Mats Petersson

6
@MatsPetersson VLA khác ở chỗ alloca: chúng bị loại bỏ ngay khi bạn rời khỏi phạm vi mà chúng được khai báo, trong khi allocakhông gian chỉ được giải phóng khi bạn rời khỏi hàm. Điều này làm cho VLA dễ theo dõi hơn nhiều alloca, tôi nghĩ.
Jens Gustedt

34
Có thể đáng nói là gcc được -fomit-frame-pointerbật theo mặc định cho x86-64.
zwol

5
@JensGustedt, vấn đề không phải là khi chúng bị vứt bỏ, vấn đề là kích thước của chúng (như alloca'ed space) không xác định được tại thời điểm biên dịch . Thông thường trình biên dịch sẽ sử dụng con trỏ khung để lấy địa chỉ của các biến cục bộ, nếu kích thước của khung ngăn xếp không thay đổi, nó có thể định vị chúng ở một độ lệch cố định so với con trỏ ngăn xếp.
vonbrand

15

Đây là tất cả về đăng ký BP / EBP / RBP trên nền tảng Intel. Thanh ghi này mặc định là phân đoạn ngăn xếp (không cần tiền tố đặc biệt để truy cập phân đoạn ngăn xếp).

EBP là lựa chọn đăng ký tốt nhất để truy cập cấu trúc dữ liệu, biến và không gian làm việc được phân bổ động trong ngăn xếp. EBP thường được sử dụng để truy cập các phần tử trên ngăn xếp liên quan đến một điểm cố định trên ngăn xếp hơn là liên quan đến TOS hiện tại. Nó thường xác định địa chỉ cơ sở của khung ngăn xếp hiện tại được thiết lập cho thủ tục hiện tại. Khi EBP được sử dụng làm thanh ghi cơ sở trong phép tính bù, độ lệch được tính toán tự động trong phân đoạn ngăn xếp hiện tại (tức là phân đoạn hiện được chọn bởi SS). Bởi vì SS không phải được chỉ định rõ ràng, mã hóa lệnh trong những trường hợp như vậy hiệu quả hơn. EBP cũng có thể được sử dụng để lập chỉ mục thành các phân đoạn có thể xác định được thông qua các thanh ghi phân đoạn khác.

(nguồn - http://css.csail.mit.edu/6.858/2017/readings/i386/s02_03.htm )

Vì trên hầu hết các nền tảng 32-bit, phân đoạn dữ liệu và phân đoạn ngăn xếp giống nhau, nên sự liên kết giữa EBP / RBP với ngăn xếp này không còn là vấn đề nữa. Trên nền tảng 64-bit cũng vậy: Kiến trúc x86-64, được AMD giới thiệu vào năm 2003, phần lớn đã không hỗ trợ phân đoạn ở chế độ 64-bit: bốn trong số các thanh ghi phân đoạn: CS, SS, DS và ES bị buộc về 0 Những trường hợp này của nền tảng x86 32-bit và 64-bit về cơ bản có nghĩa là thanh ghi EBP / RBP có thể được sử dụng, không có bất kỳ tiền tố nào, trong các lệnh bộ xử lý truy cập bộ nhớ.

Vì vậy, tùy chọn trình biên dịch mà bạn đã viết cho phép BP / EBP / RBP được sử dụng cho các phương tiện khác, ví dụ như để giữ một biến cục bộ.

Bởi "Điều này tránh các hướng dẫn để lưu, thiết lập và khôi phục con trỏ khung" có nghĩa là tránh mã sau trên mục nhập của mỗi chức năng:

push ebp
mov ebp, esp

hoặc enterhướng dẫn, rất hữu ích trên bộ xử lý Intel 80286 và 80386.

Ngoài ra, trước khi trả về hàm, mã sau được sử dụng:

mov esp, ebp
pop ebp 

hoặc leavehướng dẫn.

Các công cụ gỡ lỗi có thể quét dữ liệu ngăn xếp và sử dụng dữ liệu thanh ghi EBP được đẩy này trong khi định vị call sites, tức là để hiển thị tên của hàm và các đối số theo thứ tự chúng đã được gọi là phân cấp.

Lập trình viên có thể có câu hỏi về khung ngăn xếp không theo nghĩa rộng (rằng nó là một thực thể duy nhất trong ngăn xếp chỉ phục vụ một lệnh gọi hàm và giữ địa chỉ trả về, đối số và biến cục bộ) nhưng theo nghĩa hẹp - khi thuật ngữ stack framesđược đề cập trong bối cảnh của các tùy chọn trình biên dịch. Từ quan điểm của trình biên dịch, một khung ngăn xếp chỉ là mã vào và ra cho quy trình , đẩy một mỏ neo vào ngăn xếp - cũng có thể được sử dụng để gỡ lỗi và xử lý ngoại lệ. Các công cụ gỡ lỗi có thể quét dữ liệu ngăn xếp và sử dụng các neo này để truy tìm ngược, đồng thời xác định vị trí call sitestrong ngăn xếp, tức là để hiển thị tên của hàm theo thứ tự chúng đã được gọi là phân cấp.

Đó là lý do tại sao điều rất quan trọng đối với lập trình viên là phải hiểu khung ngăn xếp là gì về các tùy chọn trình biên dịch - bởi vì trình biên dịch có thể kiểm soát việc tạo mã này hay không.

Trong một số trường hợp, khung ngăn xếp (mã vào và ra cho quy trình) có thể bị trình biên dịch bỏ qua và các biến sẽ được truy cập trực tiếp thông qua con trỏ ngăn xếp (SP / ESP / RSP) chứ không phải là con trỏ cơ sở thuận tiện (BP / ESP / RSP). Các điều kiện để trình biên dịch bỏ qua các khung ngăn xếp đối với một số hàm có thể khác nhau, ví dụ: (1) hàm là một hàm lá (nghĩa là một thực thể cuối không gọi các hàm khác); (2) không có ngoại lệ nào được sử dụng; (3) không có thói quen nào được gọi với các tham số gửi đi trên ngăn xếp; (4) hàm không có tham số.

Việc bỏ qua các khung ngăn xếp (mã nhập và thoát cho quy trình) có thể làm cho mã nhỏ hơn và nhanh hơn, nhưng cũng có thể ảnh hưởng tiêu cực đến khả năng truy tìm lại dữ liệu trong ngăn xếp và hiển thị cho người lập trình. Đây là các tùy chọn trình biên dịch xác định trong những điều kiện nào mà một hàm phải đáp ứng để trình biên dịch cấp cho nó mã xuất và nhập khung ngăn xếp. Ví dụ, một trình biên dịch có thể có các tùy chọn để thêm mã vào và ra như vậy vào các hàm trong các trường hợp sau: (a) luôn luôn, (b) không bao giờ, (c) khi cần (xác định các điều kiện).

Quay trở lại từ tổng quát đến đặc biệt: nếu bạn sẽ sử dụng -fomit-frame-pointertùy chọn trình biên dịch GCC, bạn có thể giành chiến thắng trên cả mã đầu vào và mã thoát cho quy trình và khi có thêm một thanh ghi (trừ khi nó đã được bật theo mặc định hoặc chính nó hoặc ngầm định bởi người khác tùy chọn, trong trường hợp này, bạn đã được hưởng lợi từ việc sử dụng thanh ghi EBP / RBP và sẽ không có thêm lợi ích nào bằng cách chỉ định rõ ràng tùy chọn này nếu nó đã được sử dụng ngầm). Tuy nhiên, xin lưu ý rằng ở chế độ 16 bit và 32 bit, thanh ghi BP không có khả năng truy cập các phần 8 bit của nó như AX có (AL và AH).

Vì tùy chọn này, bên cạnh việc cho phép trình biên dịch sử dụng EBP như một thanh ghi có mục đích chung trong việc tối ưu hóa, còn ngăn chặn việc tạo mã thoát và mã nhập cho khung ngăn xếp làm phức tạp việc gỡ lỗi - đó là lý do tại sao tài liệu GCC tuyên bố rõ ràng (nhấn mạnh bất thường bằng dấu đậm style) bật tùy chọn này làm cho việc gỡ lỗi không thể xảy ra trên một số máy

Cũng xin lưu ý rằng các tùy chọn trình biên dịch khác, liên quan đến gỡ lỗi hoặc tối ưu hóa, có thể hoàn toàn -fomit-frame-pointerBẬT hoặc TẮT tùy chọn.

Tôi không tìm thấy bất kỳ thông tin chính thức nào tại gcc.gnu.org về cách các tùy chọn khác ảnh hưởng đến -fomit-frame-pointer nền tảng x86 , https://gcc.gnu.org/onlineocs/gcc-3.4.4/gcc/Optimize-Options.html chỉ nêu những điều sau:

-O cũng bật -fomit-frame-pointer trên các máy mà việc này không ảnh hưởng đến việc gỡ lỗi.

Vì vậy, không rõ ràng từ tài liệu -fomit-frame-pointersẽ được bật nếu bạn chỉ biên dịch với một -Otùy chọn duy nhất trên nền tảng x86. Nó có thể được kiểm tra theo kinh nghiệm, nhưng trong trường hợp này, các nhà phát triển GCC không có cam kết không thay đổi hành vi của tùy chọn này trong tương lai mà không cần thông báo.

Tuy nhiên, Peter Cordes đã chỉ ra trong các bình luận rằng có sự khác biệt đối với cài đặt mặc định của -fomit-frame-pointernền tảng x86-16 và nền tảng x86-32 / 64.

Tùy chọn này - -fomit-frame-pointer- cũng có liên quan đến Trình biên dịch Intel C ++ 15.0 , không chỉ với GCC:

Đối với Trình biên dịch Intel, tùy chọn này có một bí danh /Oy.

Đây là những gì Intel đã viết về nó:

Các tùy chọn này xác định liệu EBP có được sử dụng như một đăng ký mục đích chung trong việc tối ưu hóa hay không. Tùy chọn -fomit-frame-pointer và / Oy cho phép sử dụng điều này. Tùy chọn -fno-omit-frame-pointer và / Oy- không cho phép nó.

Một số trình gỡ rối mong muốn EBP được sử dụng như một con trỏ khung ngăn xếp và không thể tạo ra dấu vết ngăn xếp trừ khi điều này xảy ra. Tùy chọn -fno-omit-frame-pointer và / Oy- chỉ đạo trình biên dịch tạo mã duy trì và sử dụng EBP làm con trỏ khung ngăn xếp cho tất cả các chức năng để trình gỡ lỗi vẫn có thể tạo ra một ngăn xếp tồn đọng mà không cần làm như sau:

Đối với -fno-omit-frame-pointer: tắt tối ưu hóa với -O0 For / Oy-: tắt tối ưu hóa / O1, / O2 hoặc / O3 Tùy chọn -fno-omit-frame-pointer được đặt khi bạn chỉ định tùy chọn - O0 hoặc tùy chọn -g. Tùy chọn -fomit-frame-pointer được đặt khi bạn chỉ định tùy chọn -O1, -O2 hoặc -O3.

Tùy chọn / Oy được đặt khi bạn chỉ định tùy chọn / O1, / O2 hoặc / O3. Tùy chọn / Oy- được đặt khi bạn chỉ định tùy chọn / Od.

Sử dụng tùy chọn -fno-omit-frame-pointer hoặc / Oy- sẽ giảm số lượng thanh ghi đa năng có sẵn xuống 1 và có thể dẫn đến mã kém hiệu quả hơn một chút.

LƯU Ý Đối với hệ thống Linux *: Hiện đang xảy ra sự cố với việc xử lý ngoại lệ GCC 3.2. Do đó, trình biên dịch Intel bỏ qua tùy chọn này khi GCC 3.2 được cài đặt cho C ++ và xử lý ngoại lệ được bật (mặc định).

Xin lưu ý rằng báo giá trên chỉ liên quan đến trình biên dịch Intel C ++ 15, không liên quan đến GCC.


1
Mã 16-bit và BP mặc định thành SS thay vì DS, không thực sự phù hợp với gcc. gcc -m16tồn tại, nhưng đó là một trường hợp đặc biệt kỳ lạ về cơ bản tạo ra mã 32-bit chạy ở chế độ 16-bit bằng cách sử dụng các tiền tố ở khắp nơi. Cũng lưu ý rằng nó -fomit-frame-pointerđã được bật theo mặc định trong nhiều năm trên x86 -m32và lâu hơn trên x86-64 ( -m64).
Peter Cordes

@PeterCordes - cảm ơn bạn, tôi đã cập nhật các chỉnh sửa theo các vấn đề mà bạn đã nêu ra.
Maxim Masiutin
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.