簡體   English   中英

GCC內聯匯編不會將數據復制到輸入寄存器

[英]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:無論如何清除緩沖區的原因是什么?

另請注意,您還需要設置dldh 嘗試類似的東西:

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;
}

這里有兩件事情之一就出錯了。 我無法從你提供的信息中分辨出哪一個,但其中任何一個都會阻止它工作。

  1. 您的CPU處於實模式(16位),並且需要16位指令。 GCC正在編譯代碼,假設它將以32位模式運行; 以16位模式運行它將導致某些指令被錯誤地解釋。

    需要明確的是, GCC不是16位x86的編譯器,也不能用於編譯代碼以在實模式下運行。 雖然GCC編譯的某些代碼似乎部分在實模式下工作,但它無法可靠地正常工作。

  2. 您的CPU處於保護模式(32位)。 BIOS調用在保護模式下不起作用。

從上下文來看,看起來好像是在嘗試用C編寫操作系統內核。如果你想這樣做,你需要:

  • 盡快使CPU進入受保護或長模式,使用程序集執行此操作,然后在安全的情況下輸入C代碼。

  • 針對另一種架構,它不會遭受與x86相同的初始化“怪癖”。 ARM在這方面表現非常出色。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM