2017-07-17 68 views
4

我正在一個類似嵌入式的環境中工作,每個字節都非常珍貴,比沒有對齊訪問的附加週期要多得多。我有一個從操作系統的開發實例一些簡單的防鏽代碼:如何在編譯X86時防止函數對齊到16字節邊界?

#![feature(lang_items)] 
#![no_std] 
extern crate rlibc; 
#[no_mangle] 
pub extern fn rust_main() { 

    // ATTENTION: we have a very small stack and no guard page 

    let hello = b"Hello World!"; 
    let color_byte = 0x1f; // white foreground, blue background 

    let mut hello_colored = [color_byte; 24]; 
    for (i, char_byte) in hello.into_iter().enumerate() { 
     hello_colored[i*2] = *char_byte; 
    } 

    // write `Hello World!` to the center of the VGA text buffer 
    let buffer_ptr = (0xb8000 + 1988) as *mut _; 
    unsafe { *buffer_ptr = hello_colored }; 

    loop{} 

} 

#[lang = "eh_personality"] extern fn eh_personality() {} 
#[lang = "panic_fmt"] #[no_mangle] pub extern fn panic_fmt() -> ! {loop{}} 

我也用這個連接器腳本:

OUTPUT_FORMAT("binary") 
ENTRY(rust_main) 
phys = 0x0000; 
SECTIONS 
{ 
    .text phys : AT(phys) { 
    code = .; 
    *(.text.start); 
    *(.text*) 
    *(.rodata) 
    . = ALIGN(4); 
    } 
    __text_end=.; 
    .data : AT(phys + (data - code)) 
    { 
    data = .; 
    *(.data) 
    . = ALIGN(4); 
    } 
    __data_end=.; 
    .bss : AT(phys + (bss - code)) 
    { 
    bss = .; 
    *(.bss) 
    . = ALIGN(4); 
    } 
    __binary_end = .; 
} 

opt-level: 3優化,並使用LTO的i586的有針對性的編譯器和GNU LD鏈接包括鏈接器命令中的-O3。我也在鏈接器上嘗試了opt-level: z和耦合-Os,但是這導致代碼更大(它沒有展開循環)。就目前而言,opt-level: 3的尺寸似乎相當合理。

在將函數對齊到某個邊界時,看起來浪費了很多字節。在展開的循環之後,插入了7條nop指令,然後如預期的那樣出現無限循環。在此之後,似乎還有另一個無限循環,其前面是7個16位覆蓋指令(即xchg ax,ax而不是xchg eax,eax)。這在196字節的平面二進制文件中浪費了大約26個字節。

  • 優化器究竟在做什麼?
  • 我有什麼選擇可以禁用它?
  • 爲什麼無法訪問的代碼被包含在二進制文件中?

的完整組件下方列表:

0: c6 05 c4 87 0b 00 48 movb $0x48,0xb87c4 
    7: c6 05 c5 87 0b 00 1f movb $0x1f,0xb87c5 
    e: c6 05 c6 87 0b 00 65 movb $0x65,0xb87c6 
    15: c6 05 c7 87 0b 00 1f movb $0x1f,0xb87c7 
    1c: c6 05 c8 87 0b 00 6c movb $0x6c,0xb87c8 
    23: c6 05 c9 87 0b 00 1f movb $0x1f,0xb87c9 
    2a: c6 05 ca 87 0b 00 6c movb $0x6c,0xb87ca 
    31: c6 05 cb 87 0b 00 1f movb $0x1f,0xb87cb 
    38: c6 05 cc 87 0b 00 6f movb $0x6f,0xb87cc 
    3f: c6 05 cd 87 0b 00 1f movb $0x1f,0xb87cd 
    46: c6 05 ce 87 0b 00 20 movb $0x20,0xb87ce 
    4d: c6 05 cf 87 0b 00 1f movb $0x1f,0xb87cf 
    54: c6 05 d0 87 0b 00 57 movb $0x57,0xb87d0 
    5b: c6 05 d1 87 0b 00 1f movb $0x1f,0xb87d1 
    62: c6 05 d2 87 0b 00 6f movb $0x6f,0xb87d2 
    69: c6 05 d3 87 0b 00 1f movb $0x1f,0xb87d3 
    70: c6 05 d4 87 0b 00 72 movb $0x72,0xb87d4 
    77: c6 05 d5 87 0b 00 1f movb $0x1f,0xb87d5 
    7e: c6 05 d6 87 0b 00 6c movb $0x6c,0xb87d6 
    85: c6 05 d7 87 0b 00 1f movb $0x1f,0xb87d7 
    8c: c6 05 d8 87 0b 00 64 movb $0x64,0xb87d8 
    93: c6 05 d9 87 0b 00 1f movb $0x1f,0xb87d9 
    9a: c6 05 da 87 0b 00 21 movb $0x21,0xb87da 
    a1: c6 05 db 87 0b 00 1f movb $0x1f,0xb87db 
    a8: 90      nop 
    a9: 90      nop 
    aa: 90      nop 
    ab: 90      nop 
    ac: 90      nop 
    ad: 90      nop 
    ae: 90      nop 
    af: 90      nop 
    b0: eb fe     jmp 0xb0 
    b2: 66 90     xchg %ax,%ax 
    b4: 66 90     xchg %ax,%ax 
    b6: 66 90     xchg %ax,%ax 
    b8: 66 90     xchg %ax,%ax 
    ba: 66 90     xchg %ax,%ax 
    bc: 66 90     xchg %ax,%ax 
    be: 66 90     xchg %ax,%ax 
    c0: eb fe     jmp 0xc0 
    c2: 66 90     xchg %ax,%ax 
+0

我不知道Rust,但反彙編中的第二個無限循環可能是源代碼中的第二個無限循環。給循環分支目標16字節對齊是一種非常常見的性能優化,但顯然無限循環的性能可能並不重要。 –

+1

嘗試將'-C llvm-args = -align-all-blocks = 1'添加到'rustc'選項。 – red75prime

+0

'pub extern panic_fmt()'的代碼包含在二進制文件中,可能是因爲您將其聲明爲導出的公共函數或因爲[未聲明'panic_fmt' correcly](https://doc.rust-lang.org /核心/#如何使用的最核心庫)。我目前無法構建您的代碼,所以我無法驗證這一點。 – red75prime

回答

6

作爲Ross states,調心功能和分支點爲16個字節是由Intel推薦一個共同的x86優化,雖然它可以偶爾會降低效率,如在你的情況。對於編譯器來優化決定是否對齊是一個難題,我相信LLVM只是選擇始終對齊。 See more info on Performance optimisations of x86-64 assembly - Alignment and branch prediction

由於red75prime's comment hints(但沒有說明),LLVM使用align-all-blocks的值作爲分支點的字節對齊方式,因此將其設置爲1將禁用對齊方式。請注意,這適用於全球範圍,建議使用比較基準。