簡體   English   中英

如果 `objdump -d --start-address` 從 x86 指令的中間開始打印會發生什么?

[英]What happens if `objdump -d --start-address` starts printing from the middle of an x86 instruction?

...換句話說,x86-64 是否是一個獨特的可解碼代碼,無論我從哪里開始解碼,最終總是會產生正確的反匯編代碼?

假設這(基本事實)是 ELF 文件的一部分。 第一條指令有六個字節長,從 0x5de5a2 到 0x5de5a7:

  ...
  5de5a2:       0f 84 d0 00 00 00       je     5de678 <_ZN16DefNewGeneration22copy_to_survivor_spaceEP7oopDesc.constprop.0+0x138>
  5de5a8:       48 c1 e8 03             shr    rax,0x3
  5de5ac:       83 e0 0f                and    eax,0xf
  ...

如果我執行objdump -d --start-address=0x5de5a2 ,則 output 確實是正確的。

如果我故意傳入一個將“拆分”第一條指令的起始地址,例如,let start-address=0x5de524,會發生什么情況?

這就是我得到的: objdump -d --start-adress=0x5de5a4

  ...
  5de5a4:       d0 00                   rol    BYTE PTR [rax],1
  5de5a6:       00 00                   add    BYTE PTR [rax],al
  5de5a8:       48 c1 e8 03             shr    rax,0x3
  5de5ac:       83 e0 0f                and    eax,0xf
  ...

在這種特定情況下, 0x5de5a2: je 5de678被反匯編成垃圾,但幸運的是,0x56e5a8 之后的所有代碼仍然被正確解碼。

我想問的是,這是我可以一直依賴的財產嗎? 我可以讓objdump在文本部分的任意起始地址開始反匯編 x86 ELF 文件,即使起始地址會拆分合法指令,但期望 objdump “最終”打印正確的反匯編代碼嗎?

附加問題:起始地址未對齊對反匯編正確性的影響有多大? 此屬性是否也適用於其他 ISA?

X86 代碼不是自同步的,如果你在一個不是指令正確開始的地址開始反匯編,你可能會或可能不會在一些錯誤指令后得到好的代碼,但這不是你可以指望的).

在 x86 的早期,甚至有人知道寫代碼依賴於 x86 代碼的非自同步性質,會有故意命中指令中間的 jmp 指令等等。

有一些 ISA 是自同步的,特別是具有固定長度指令的機器根據定義幾乎是自同步的。 我不知道有哪些機器像 x86 那樣具有可變長度指令並且是自同步的,但我絕不是專家。

“最終”打印出正確的反匯編代碼?

Nitpick:這已經是該起點的正確反匯編。 如果您跳到那里,這就是 CPU 將執行的內容。 x86 機器代碼是一個字節 stream ,它不是自同步的(與 UTF-8 不同),沒有任何東西將字節標記為不是指令的開始。 這與 GDB 不允許您在layout asm TUI 模式下向后滾動的原因相同,如果它附近沒有要反匯編的符號。

但是您真正要問的是,是的,通常解碼將與編譯器在幾條指令中預期的指令邊界同步,在 objdump 首先工作的非混淆可執行文件中1 許多字節序列形成短指令,並且有相當多的 1 字節操作碼,您可以在其他指令中找到其中一些作為立即數或 ModRM/SIB 字節。 (例如00 00add [rax], al .)在 x86-64 中,一些字節當前未定義為操作碼,因此反匯編程序通常反匯編為.byte xyz並將下一個字節視為可能的指令開始。 在 32 位模式下,其中大部分是 1 字節指令,其中一些是 2 字節(push/pop 段 regs 和 BCD 指令,如 AAA 或 AAM)。

手工制作的機器代碼序列可能會在更長的時間內支持不同的解碼,在 memory 的相同字節中疊加兩個不同的指令序列。

相同的字節塊從不同的起點解碼不同的方式有時仍被用作混淆技術。 例如向前跳轉 1 個字節,或向后跳轉到已經以不同方式運行的內容。 通常其中一個執行並不是很有用,只是為了混淆反匯編程序。 (這對性能不利,尤其是在 L1i 緩存中標記指令邊界的 CPU 以及具有 uop 緩存的 CPU 上。)

在極端的代碼大小優化中(例如代碼高爾夫或演示場景),諸如在進入循環的第一次迭代時跳過 4 個字節之類的東西可以用一個 1 字節的操作碼來完成,用於test eax, imm32的開始,如Tips for golfing in x86/x64 機器代碼

有關的:


腳注 1:設計用於處理潛在混淆二進制文件的更復雜的反匯編程序將從某個入口點開始,然后直接跳轉以找到其他起點進行反匯編,希望從有效起點覆蓋.text部分的所有字節以執行。 間接跳轉和從未采用的條件分支仍然可以欺騙他們。

GCC 和 clang 使 x86 可執行文件易於反匯編,在 GCC 的情況下,從字面上打印與.p2align指令混合的 asm 文本並將其組裝。 我聽說 MSVC 有時是/不是那么微不足道,但我忘記了細節。 現在 MSVC 只是在函數之間使用int3 (0xcc) 填充。

另請參閱為什么編譯器將數據放在 PE 和 ELF 文件的 .text(code) 部分以及 CPU 如何區分數據和代碼? - 他們沒有,請參閱我的回答,了解 x86 沒有好處的原因。 .rodata可能與.text連續,但分開分組。 一個提議的二進制隨機化器需要處理這個問題,因為混淆的可執行文件可能會這樣做,而不是因為正常的編譯器 output。


其他 ISA

ARM “文字池”在函數之間填充數據(因為有一個相對於 PC 的尋址模式,位移有限,所以它對常量很有用)。 對於 Thumb 模式的可變長度指令,如果數據恰好設置了表明它是 32 位指令的兩個 16 位塊中的第一個的位集,那么反匯編可能會有一點歧義,但它來了就在 function 開始之前。

大多數具有可變長度指令的現代 ISA 都比 x86 設計得更好,容易解碼,因為初始塊采用一致的方式來表示它是更長指令的開始。 (例如 1 位像 Thumb,或者我認為 RV32C 或 Agner Fog 的紙上 ISA ForwardCom的多個位,如果我沒記錯的話。)

因此很容易恢復同步,但如果您從較長指令的第二個或后面的塊開始,第一個解碼可能仍然“很奇怪”。 與 UTF-8 不同,機器代碼不會在每個塊上花費一點來表示它是一個延續,而不是新指令的開始。 在 UTF-8 文本中查找很重要,但機器代碼通常只是執行,因此設計選擇不同。

暫無
暫無

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

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