2011-08-26 21 views
6

Tôi đang thực hiện một số thử nghiệm với lắp ráp x86-64. Sau khi biên soạn chức năng giả này:đăng ký đối số gcc tràn trên x86-64

long myfunc(long a, long b, long c, long d, 
      long e, long f, long g, long h) 
{ 
    long xx = a * b * c * d * e * f * g * h; 
    long yy = a + b + c + d + e + f + g + h; 
    long zz = utilfunc(xx, yy, xx % yy); 
    return zz + 20; 
} 

Với gcc -O0 -g Tôi đã ngạc nhiên khi thấy những điều sau đây vào đầu lắp ráp của chức năng:

0000000000400520 <myfunc>: 
    400520:  55      push rbp 
    400521:  48 89 e5    mov rbp,rsp 
    400524:  48 83 ec 50    sub rsp,0x50 
    400528:  48 89 7d d8    mov QWORD PTR [rbp-0x28],rdi 
    40052c:  48 89 75 d0    mov QWORD PTR [rbp-0x30],rsi 
    400530:  48 89 55 c8    mov QWORD PTR [rbp-0x38],rdx 
    400534:  48 89 4d c0    mov QWORD PTR [rbp-0x40],rcx 
    400538:  4c 89 45 b8    mov QWORD PTR [rbp-0x48],r8 
    40053c:  4c 89 4d b0    mov QWORD PTR [rbp-0x50],r9 
    400540:  48 8b 45 d8    mov rax,QWORD PTR [rbp-0x28] 
    400544:  48 0f af 45 d0   imul rax,QWORD PTR [rbp-0x30] 
    400549:  48 0f af 45 c8   imul rax,QWORD PTR [rbp-0x38] 
    40054e:  48 0f af 45 c0   imul rax,QWORD PTR [rbp-0x40] 
    400553:  48 0f af 45 b8   imul rax,QWORD PTR [rbp-0x48] 
    400558:  48 0f af 45 b0   imul rax,QWORD PTR [rbp-0x50] 
    40055d:  48 0f af 45 10   imul rax,QWORD PTR [rbp+0x10] 
    400562:  48 0f af 45 18   imul rax,QWORD PTR [rbp+0x18] 

gcc rất lạ tràn tất cả các tham số đăng ký vào stack và sau đó mất chúng từ bộ nhớ cho các hoạt động tiếp theo.

Điều này chỉ xảy ra trên -O0 (với -O1 không có vấn đề gì), nhưng vẫn vậy, tại sao? Điều này có vẻ như một sự tối ưu hóa chống lại tôi - tại sao gcc làm điều đó?

+6

Tôi nghĩ rằng bạn có thể có nó ngược. Tôi khá chắc chắn ở trên là làm thế nào GCC luôn luôn (ban đầu) tạo ra mã, nó chỉ là bạn sẽ không bình thường nhìn thấy nó như nó được tối ưu hóa một cách trivially (nhưng tất nhiên chỉ khi tối ưu hóa được kích hoạt). – user786653

+0

Đây không phải là chống tối ưu hóa, nó chỉ là không tối ưu hóa. – hirschhornsalz

+0

Tôi vừa xem ví dụ này ở đâu đó: http://eli.thegreenplace.net/2011/09/06/stack-frame-layout-on-x86-64/ :-) –

Trả lời

7

Tôi không phải là chuyên gia nội bộ của GCC, nhưng tôi sẽ cung cấp cho nó một shot. Thật không may, hầu hết các thông tin về việc phân bổ đăng ký GCC và tràn có vẻ đã lỗi thời (tham khảo các tệp như local-alloc.c không tồn tại nữa).

Tôi đang xem mã nguồn của gcc-4.5-20110825.

Trong GNU C Compiler Internals, có nghĩa là mã chức năng ban đầu được tạo bởi expand_function_start trong gcc/function.c. Ở đó chúng ta tìm thấy những điều sau đây để xử lý các thông số:

4462 /* Initialize rtx for parameters and local variables. 
4463  In some cases this requires emitting insns. */ 
4464 assign_parms (subr); 

Trong assign_parms mã để xử lý trong đó mỗi đối số được lưu trữ được như sau:

3207  if (assign_parm_setup_block_p (&data)) 
3208   assign_parm_setup_block (&all, parm, &data); 
3209  else if (data.passed_pointer || use_register_for_decl (parm)) 
    assign_parm_setup_reg (&all, parm, &data); 
3211  else 
3212   assign_parm_setup_stack (&all, parm, &data); 

xử lý tổng hợp các kiểu dữ liệu và không áp dụng trong trường hợp này và vì dữ liệu không được truyền như một con trỏ, GCC kiểm tra use_register_for_decl.

Đây là phần có liên quan là:

1972 if (optimize) 
1973  return true; 
1974 
1975 if (!DECL_REGISTER (decl)) 
1976  return false; 

DECL_REGISTER kiểm tra liệu các biến được khai báo với từ khóa register. Và bây giờ chúng tôi có câu trả lời của chúng tôi: Hầu hết các tham số đều nằm trên ngăn xếp khi tối ưu hóa không được bật và sau đó được xử lý bởi assign_parm_setup_stack. Các tuyến đường được thực hiện thông qua mã nguồn trước khi nó kết thúc tràn giá trị là hơi phức tạp hơn cho các đối số con trỏ, nhưng có thể được truy tìm trong cùng một tập tin nếu bạn tò mò.

Tại sao GCC tràn tất cả đối số và biến cục bộ với tối ưu hóa bị vô hiệu hóa? Để giúp gỡ lỗi. Xem xét chức năng đơn giản này:

1 extern int bar(int); 
2 int foo(int a) { 
3   int b = bar(a | 1); 
4   b += 42; 
5   return b; 
6 } 

Biên soạn với gcc -O1 -c này tạo ra sau trên máy tính của tôi:

0: 48 83 ec 08    sub $0x8,%rsp 
4: 83 cf 01    or  $0x1,%edi 
7: e8 00 00 00 00   callq c <foo+0xc> 
c: 83 c0 2a    add $0x2a,%eax 
f: 48 83 c4 08    add $0x8,%rsp 
13: c3      retq 

nào là tốt trừ khi bạn phá vỡ trên dòng 5 và cố gắng để in giá trị của một, bạn nhận được

(gdb) print a 
$1 = <value optimized out> 

Vì đối số sẽ bị ghi đè vì không được sử dụng sau khi gọi đến bar.

6

Một vài lý do:

  1. Trong trường hợp tổng quát, một cuộc tranh cãi với một hàm phải được đối xử như một biến địa phương vì nó có thể được lưu trữ hoặc địa chỉ của nó đã được thực hiện trong phạm vi chức năng. Do đó, đơn giản nhất là chỉ phân bổ một ngăn xếp ngăn xếp cho mọi đối số.
  2. Thông tin gỡ lỗi trở nên đơn giản hơn nhiều khi phát ra với vị trí ngăn xếp: giá trị của đối số luôn ở vị trí cụ thể, thay vì di chuyển giữa các thanh ghi và bộ nhớ.

Khi bạn nhìn vào mã -O0 nói chung, hãy xem xét ưu tiên hàng đầu của trình biên dịch là giảm thời gian biên dịch càng nhiều càng tốt và tạo ra thông tin gỡ lỗi chất lượng cao.

+1

Có, và không có tối ưu hóa, trình biên dịch đặc biệt làm cho tất cả các dòng độc lập, luôn tải lại từ các biến thực và lưu trữ ngay lập tức, cho phép bạn di chuyển CPU sang một dòng khác hoặc thay đổi giá trị của bất kỳ biến nào trong trình gỡ rối và hoạt động chính xác. – doug65536

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