[英]GCC inline assembly isn't copying data to input registers
我試圖使用BIOS中斷使用以下代碼讀取硬盤扇區:
int readSec(char sector, unsigned char out[]) {
char error = 0;
int address = (int)out;
__asm__ __volatile__("xorw %ax, %ax;" //clear all registers
"xorw %bx, %bx;"
"xorw %cx, %cx;"
"xorw %dx, %dx;");
__asm__ __volatile__("movw $0x00, %%ax; movw %%ax,%%es;" //clear the memory location that the sector will be read into
"movw $0x0000, %%ax;"
"movw $512, %%cx;"
"movw %w0, %%di;"
"rep stosw;"::"b" (address));
__asm__ __volatile__(
"movb $0x02, %%ah;" //read the sector
"movb $0x01, %%al;"
"movw %w2, %%bx;"
"movb %b1, %%cl;"
"int $0x13;"
"sbb %w0, %w0;": "=r" (error): "m" (sector), "m" (address));
return error;
}
運行此代碼后,沒有任何反應。
為了調試代碼,我查看了反匯編,令我驚訝的是“地址”變量永遠不會被復制到di
- 而是復制了一個空寄存器。 這是函數的反匯編(在右邊)和函數本身(在左邊):
從圖中可以看出, bx
寄存器被歸零(因為它在第一個匯編程序塊中),然后在到達movw %w0, %%di
行時,它只使用歸零的bx
寄存器mov %bx,%di
。 為什么海灣合作委員會這樣做? 顯然,將空寄存器的值復制到di
不會將address
復制到它。
編輯:我正在使用GCC編譯此代碼並在虛擬機中運行它。 這不是所有代碼,我只需要知道為什么將空寄存器復制到%di
編輯2:我從(int) & out;
刪除了&符號(int) & out;
正如nos所建議的那樣
好的,讓我們看看它為你指定的內容生成了什么。 你有:
:
"movw %w0, %%di;"
...::"b" (address));
這告訴編譯器'將地址中的值復制到%ebx(“b”約束),並生成movew %bx,%di
指令將其復制到%di。 如果你看一下生成的代碼,那就是它的作用:
7e62: lea 0xc(%ebp),%ebx
7e83: mov %bx,%di
它將out
指針的地址加載到%ebx中作為初始化address
一部分,然后再使用它。
問題是你已經放入了另一個asm代碼塊,它清除了這兩點之間的ebx而沒有告訴編譯器。 要解決這個問題,你需要在第一個塊上設置'clobber' - 追加:
:::"eax","ebx","ecx","edx".
執行此操作后,第一個塊本質上是一個noop,告訴編譯器保存並恢復塊周圍的4個寄存器中的值,否則什么都不做。 所以你應該完全刪除它。
此外,第二個asm塊相當於:
memset(&out, 0, 1024);
除了效率低得多。 它也只是覆蓋堆棧的一大塊(因為out
只是堆棧上的指針),這可能導致此代碼在嘗試返回時崩潰(返回地址已被0覆蓋)。
一般來說,在C中編寫代碼並且只使用內聯asm代表無法用C表示的代碼( int $0x13
指令並將進位標志轉換為錯誤代碼),你會好得多。
就像是:
int readSec(char sector, unsigned char out[]) {
int error;
memset(out, 0, 1024); // clear buffer pointed at by out, rather than stack!
__asm__ volatile(
"int $0x13;"
"sbb %0,%0"
: "=r"(error) : "a"(0x201), "c"(sector), "b"(out))
return error;
}
你沒有告訴編譯器哪些寄存器你正在破壞,所以它不知道它可以安全使用哪些。 很可能,從編譯器的角度來看,你所追求的地址是在bx寄存器中。 您正在清除程序集中的該寄存器的事實對編譯器是不可見的。
所以...你可以嘗試使用Clobbers參數(在輸出之后)清除寄存器的塊的asm
。
除了沒有正確使用clobbers之外,這是內聯asm的錯誤使用,除了實際的int 0x13
指令外,你應該在C中做所有事情。 此外,不保留asm塊之間的寄存器,不清楚你是否期望這樣。
PS:無論如何清除緩沖區的原因是什么?
另請注意,您還需要設置dl
和dh
。 嘗試類似的東西:
int readSec(unsigned sector, void* out) {
int error = 0;
unsigned sector_and_track = sectpr + xxxx;
unsigned drive_and_head = xxxx;
memset(out, 0, 512);
__asm__ __volatile__(
"int 0x13; sbb %0, %0"
: "=r" (error) : "a" (0x0201), "b" (out), "c" (sector_and_track), "d" (drive_and_head));
return error;
}
這里有兩件事情之一就出錯了。 我無法從你提供的信息中分辨出哪一個,但其中任何一個都會阻止它工作。
您的CPU處於實模式(16位),並且需要16位指令。 GCC正在編譯代碼,假設它將以32位模式運行; 以16位模式運行它將導致某些指令被錯誤地解釋。
需要明確的是, GCC不是16位x86的編譯器,也不能用於編譯代碼以在實模式下運行。 雖然GCC編譯的某些代碼似乎部分在實模式下工作,但它無法可靠地正常工作。
您的CPU處於保護模式(32位)。 BIOS調用在保護模式下不起作用。
從上下文來看,看起來好像是在嘗試用C編寫操作系統內核。如果你想這樣做,你需要:
盡快使CPU進入受保護或長模式,使用程序集執行此操作,然后在安全的情況下輸入C代碼。
針對另一種架構,它不會遭受與x86相同的初始化“怪癖”。 ARM在這方面表現非常出色。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.