2015-03-16 12 views
11

Trong khi cố gắng để làm cho một số mã cũ làm việc trở lại (https://github.com/chaos4ever/chaos/blob/master/libraries/system/system_calls.h#L387, FWIW) Tôi phát hiện ra rằng một số ngữ nghĩa của gcc dường như đã thay đổi theo một cách khá tinh tế nhưng vẫn nguy hiểm trong 10-15 năm gần đây nhất ...: PGCC/x86 nội tuyến asm: Làm thế nào để bạn nói với gcc rằng phần lắp ráp nội tuyến sẽ sửa đổi% esp?

Mã được sử dụng để hoạt động tốt với các phiên bản cũ hơn của gcc, như 2,95. Dù sao, đây là mã:

static inline return_type system_call_service_get(const char *protocol_name, service_parameter_type *service_parameter, 
    tag_type *identification) 
{ 
    return_type return_value; 

    asm volatile("pushl %2\n" 
       "pushl %3\n" 
       "pushl %4\n" 
       "lcall %5, $0" 
       : "=a" (return_value), 
        "=g" (*service_parameter) 
       : "g" (identification), 
        "g" (service_parameter), 
        "g" (protocol_name), 
        "n" (SYSTEM_CALL_SERVICE_GET << 3)); 

    return return_value; 
} 

Vấn đề với đoạn code trên được rằng gcc (4.7 trong trường hợp của tôi) sẽ biên dịch này để mã asm sau (AT & cú pháp T):

# 392 "../system/system_calls.h" 1 
pushl 68(%esp) # This pointer (%esp + 0x68) is valid when the inline asm is entered. 
pushl %eax 
pushl 48(%esp) # ...but this one is not (%esp + 0x48), since two dwords have now been pushed onto the stack, so %esp is not what the compiler expects it to be 
lcall $456, $0 

# Restoration of %esp at this point is done in the called method (i.e. lret $12) 

Vấn đề: Các biến (identificationprotocol_name) nằm trong ngăn xếp trong ngữ cảnh gọi. Vì vậy, gcc (với tối ưu hóa hóa ra, không chắc chắn nếu nó quan trọng) sẽ chỉ nhận được các giá trị từ đó và đưa nó cho phần nội tuyến asm. Nhưng vì tôi đang đẩy nội dung trên ngăn xếp, các bù trừ rằng tính toán gcc sẽ bị tắt bởi 8 trong cuộc gọi thứ ba (pushl 48(%esp)). :)

Điều này đã cho tôi một thời gian dài để tìm ra, nó không phải là tất cả rõ ràng với tôi lúc đầu.

Cách dễ nhất xung quanh điều này là tất nhiên để sử dụng ràng buộc đầu vào r, để đảm bảo rằng giá trị trong sổ đăng ký thay thế. Nhưng có cách nào khác tốt hơn không? Dĩ nhiên, một cách rõ ràng là viết lại toàn bộ giao diện gọi hệ thống để không đẩy nội dung vào ngăn xếp ở vị trí đầu tiên (và sử dụng thanh ghi thay thế, ví dụ như Linux), nhưng đó không phải là tái cấu trúc mà tôi cảm thấy thích làm tối nay ...

Có cách nào để báo cho gcc nội tuyến asm rằng "ngăn xếp dễ bay hơi" không? Các bạn đã xử lý những thứ như thế này trong quá khứ?


Cập nhật sau cùng buổi tối: Tôi đã tìm thấy một liên quan gcc ML ren (https://gcc.gnu.org/ml/gcc-help/2011-06/msg00206.html) nhưng nó không có vẻ để giúp đỡ. Dường như việc chỉ định %esp trong danh sách clobber nên làm cho nó bù trừ từ %ebp thay vào đó, nhưng nó không hoạt động và tôi nghi ngờ -O2 -fomit-frame-pointer có hiệu lực ở đây. Tôi đã bật cả hai cờ này.

+0

Iirc thêm "cc" và/hoặc "bộ nhớ" vào danh sách clobber thực hiện điều này. Đôi khi thêm biến động vào asm() sẽ ngăn trình biên dịch tối ưu hóa quá mức. – technosaurus

+0

Làm cách nào để thực hiện 'đẩy 8 +% 4'? –

+2

Nhân tiện: [% rsp trong danh sách clobber bị bỏ qua một cách thầm lặng] (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=52813). –

Trả lời

2

Điều gì làm việc và những gì không:

  1. tôi đã cố gắng bỏ qua . Không ảnh hưởng gì. Tôi đã bao gồm %esp, espsp trong số list of clobbers.

  2. Tôi đã thử bỏ qua và -O3. Điều này thực sự tạo mã hoạt động, vì nó dựa trên %ebp thay vì %esp.

    pushl 16(%ebp) 
    pushl 12(%ebp) 
    pushl 8(%ebp) 
    lcall $456, $0 
    
  3. Tôi đã thử với chỉ có -O3 và không quy định tại dòng lệnh của tôi. Tạo mã hỏng, bị hỏng (dựa trên %esp là hằng số trong toàn bộ khối lắp ráp, tức làkhông có khung ngăn xếp).

  4. Tôi đã thử bỏ qua và chỉ sử dụng -O2. Mã bị hỏng, không có khung ngăn xếp.

  5. Tôi đã thử chỉ bằng cách sử dụng `-O1. Mã bị hỏng, không có khung ngăn xếp.

  6. Tôi đã thử thêm cc làm bảng điều khiển. Không thể làm được, không tạo ra bất kỳ sự khác biệt nào.

  7. Tôi đã thử thay đổi các ràng buộc đầu vào thành ri, cung cấp mã đầu ra & đầu ra bên dưới. Điều này tất nhiên hoạt động nhưng hơi kém thanh lịch hơn tôi đã hy vọng. Sau đó, một lần nữa, perfect is the enemy of good vì vậy có lẽ tôi sẽ phải sống với điều này bây giờ. mã C

Input:

static inline return_type system_call_service_get(const char *protocol_name, service_parameter_type *service_parameter, 
    tag_type *identification) 
{ 
    return_type return_value; 

    asm volatile("pushl %2\n" 
       "pushl %3\n" 
       "pushl %4\n" 
       "lcall %5, $0" 
       : "=a" (return_value), 
        "=g" (*service_parameter) 
       : "ri" (identification), 
        "ri" (service_parameter), 
        "ri" (protocol_name), 
        "n" (SYSTEM_CALL_SERVICE_GET << 3)); 

    return return_value; 
} 

Output code asm. Như có thể thấy, bằng cách sử dụng thanh ghi thay thế mà nên luôn luôn được an toàn (nhưng có thể phần nào ít performant kể từ khi trình biên dịch để di chuyển công cụ xung quanh):

#APP 
# 392 "../system/system_calls.h" 1 
pushl %esi 
pushl %eax 
pushl %ebx 
lcall $456, $0 
Các vấn đề liên quan