Ado*_*uka 4 windows assembly executable disassembly
我想了解可执行文件的工作原理。我希望详细了解一个非常具体的示例将使我能够做到这一点。我的最终目标(可能过于雄心勃勃)是获取一个 hello-world .exe 文件(使用 C 编译器编译并链接)并详细了解它是如何加载到内存中并由 x86 处理器执行的。如果我成功了,我想写一篇文章和/或制作关于它的视频,因为我还没有在互联网上找到这样的东西。
我想问的具体问题用粗体标出。当然,非常欢迎任何进一步的建议和来源做类似的事情。非常感谢您的帮助!
此答案概述了 C 代码在作为程序进入物理内存之前所经历的过程。我还不确定我有多想研究 C 代码是如何编译的。有没有办法在汇编之前查看 C 编译器生成的汇编代码?我可能会认为了解加载和链接过程是值得的。与此同时,我需要了解的最重要的部分是
我对 PA 格式有一个非常基本的了解(这种理解将在“到目前为止我学到的东西”一节中概述)并且我认为那里提供的资料应该足够了,我只需要再研究一下,直到我足够了解基本的 Hello-World 程序。当然,非常欢迎有关此主题的更多来源。
对于 x86 来说,将字节码转换为汇编代码(反汇编)似乎相当困难。尽管如此,我很想了解更多关于它的信息。您将如何反汇编一个短字节代码段?
我仍在寻找一种查看进程内存(分配给它的虚拟内存)内容的方法。我已经研究过 windows-kernel32.dll 函数,例如ReadProcessMemory
但还不能让它工作。我也很奇怪似乎没有(免费)工具可用于此目的。再加上对加载的理解,我也许就能理解进程是如何从 RAM 运行的。此外,我正在寻找允许查看整个进程虚拟内存内容的汇编程序员的调试工具。我目前搜索的起点是这个问题。您对我如何查看和理解从 RAM 加载和进程执行有进一步的建议吗?
这个 StackOverflow 问题的其余部分详细描述了我到目前为止所学到的知识并提供了各种来源。它旨在可复制并帮助任何试图理解这一点的人。但是,我对目前看到的示例仍然有一些疑问。
在 Windows 中,可执行文件遵循PA 格式。在官方文件和这篇文章给出的格式的一个很好的概述。该格式描述了 .exe 文件中各个字节的含义。开头是我不会担心的 DOS 程序(出于遗留原因包括在内)。然后是一堆标头,它们提供有关可执行文件的信息。实际的文件内容被分成具有名称的部分,例如“.rdata”。在文件头之后,还有段头,它告诉您文件的哪些部分是哪个段以及每个段的作用(例如,如果它包含可执行代码)。
可以使用诸如 dumpbin(用于查看二进制文件的微软工具)之类的工具来解析标题和部分。为了与 dumpbin 输出进行比较,可以使用十六进制编辑器甚至使用 Powershell(命令Format-Hex -Path <Path to file>
)直接查看文件的十六进制代码。
我为一个非常简单的程序执行了这些步骤,它什么都不做。这是代码:
; NASM assembler programm. Does nothing. Stores string in code section.
; Adapted from stackoverflow.com/a/1029093/9988487
global _main
section .text
_main:
hlt
db 'Hello, World'
Run Code Online (Sandbox Code Playgroud)
我用 NASM (command nasm -fwin32 filename.asm
)组装了它,并将它与 VS2019 ( link /subsystem:console /nodefaultlib /entry:main test.obj
)附带的链接器链接起来。这是改编自这个答案,它演示了如何使用 WinAPI 调用为 Windows 制作一个 hello-world 程序。该程序在 Windows 10 上运行,并在没有输出的情况下终止。运行大约需要2 秒,这似乎很长,让我觉得可能有一些错误?
然后我查看了 dumpbin 输出:
D:\ASM>dumpbin test.exe /ALL
Microsoft (R) COFF/PE Dumper Version 14.22.27905.0
Copyright (C) Microsoft Corporation. All rights reserved.
Dump of file test.exe
PE signature found
File Type: EXECUTABLE IMAGE
FILE HEADER VALUES
14C machine (x86)
2 number of sections
5E96C000 time date stamp Wed Apr 15 10:04:16 2020
0 file pointer to symbol table
0 number of symbols
E0 size of optional header
102 characteristics
Executable
32 bit word machine
OPTIONAL HEADER VALUES
10B magic # (PE32)
14.22 linker version
200 size of code
200 size of initialized data
0 size of uninitialized data
1000 entry point (00401000)
1000 base of code
2000 base of data
400000 image base (00400000 to 00402FFF)
1000 section alignment
200 file alignment
<further header values omitted ...>
SECTION HEADER #1
.text name
E virtual size
1000 virtual address (00401000 to 0040100D)
200 size of raw data
200 file pointer to raw data (00000200 to 000003FF)
0 file pointer to relocation table
0 file pointer to line numbers
0 number of relocations
0 number of line numbers
60000020 flags
Code
Execute Read
RAW DATA #1
00401000: F4 48 65 6C 6C 6F 2C 20 57 6F 72 6C 64 0A ôHello, World.
SECTION HEADER #2
.rdata name
58 virtual size
2000 virtual address (00402000 to 00402057)
200 size of raw data
400 file pointer to raw data (00000400 to 000005FF)
0 file pointer to relocation table
0 file pointer to line numbers
0 number of relocations
0 number of line numbers
40000040 flags
Initialized Data
Read Only
RAW DATA #2
00402000: 00 00 00 00 00 C0 96 5E 00 00 00 00 0D 00 00 00 .....À.^........
00402010: 3C 00 00 00 1C 20 00 00 1C 04 00 00 00 00 00 00 <.... ..........
00402020: 00 10 00 00 0E 00 00 00 2E 74 65 78 74 00 00 00 .........text...
00402030: 00 20 00 00 1C 00 00 00 2E 72 64 61 74 61 00 00 . .......rdata..
00402040: 1C 20 00 00 3C 00 00 00 2E 72 64 61 74 61 24 7A . ..<....rdata$z
00402050: 7A 7A 64 62 67 00 00 00 zzdbg...
Debug Directories
Time Type Size RVA Pointer
-------- ------- -------- -------- --------
5E96C000 coffgrp 3C 0000201C 41C
Summary
1000 .rdata
1000 .text
Run Code Online (Sandbox Code Playgroud)
文件头字段“特性”是标志的组合。特别102h = 1 0000 0010b
是两个设置标志(根据 PE 格式文档)是IMAGE_FILE_EXECUTABLE_IMAGE
和IMAGE_FILE_BYTES_REVERSED_HI
。后者有说明
IMAGE_FILE_BYTES_REVERSED_HI:
大端:MSB 在内存中位于 LSB 之前。此标志已弃用,应为零。
我问自己:为什么现代汇编器和现代链接器会产生不推荐使用的标志?
文件中有 2 个部分。该部分.text
是在汇编代码中定义的(并且是唯一包含可执行代码的部分,如其标头中所指定)。我不知道第二部分“.rdata”(名称似乎指的是“可读数据”)是什么或在这里做什么。它为什么被创建?我怎么知道?
我使用 dumpbin 来反汇编 .exe 文件(命令dumpbin test.exe /DISASM
)。它得到了hlt
正确的“你好,世界”。string 被(可能不幸地)解释为可执行命令。我想这几乎不能归咎于反汇编程序。但是,如果我理解正确(我在汇编编程方面没有实际经验),将数据放入代码段并非闻所未闻(这是在我研究汇编编程时发现的几个示例中完成的)。有没有更好的方法来分解这个,这样可以更好地重现我的汇编代码?另外,编译器有时会以这种方式将数据放入代码段吗?
在某些方面,这是一个非常广泛的问题,因此可能无法生存。网上的资料全是,继续看,不复杂,不值得一篇论文或视频。
因此,您有一个粗略的想法,即编译器将用一种语言编写的程序转换为另一种语言,无论是汇编语言还是机器代码或其他语言。
然后是文件格式,有许多不同的格式,我们都使用术语“二进制”,但同样,不同的格式。理想情况下,它们使用某种形式的编码包含机器代码和数据或有关数据的信息。
暂时打算用ARM,定长指令易拆易读等。
#define ONE 1
unsigned int x;
unsigned int y = 5;
const unsigned int z = 7;
unsigned int fun ( unsigned int a )
{
return(a+ONE);
}
Run Code Online (Sandbox Code Playgroud)
和 gnu gcc/binutils 因为它是众所周知的,广泛使用的,你可以用它在你的 wintel 机器上制作程序。我运行 Linux,所以您会看到 elf 而不是 exe,但这只是您所要求的文件格式。
arm-none-eabi-gcc -O2 -c so.c -save-temps -o so.o
Run Code Online (Sandbox Code Playgroud)
这个工具链(链接的工具链,例如编译器 -> 汇编器 -> 链接器)是 Unix 风格和模块化的。您将有一个用于目标的汇编器,所以不确定为什么要重新发明它,并且通过查看汇编输出来调试编译器比尝试直接使用机器代码要容易得多。但是有些人喜欢爬山只是因为它在那里而不是四处走动,一些工具直接用于机器代码只是因为它在那里。
这个特定的编译器有这个保存临时文件的功能,gcc 本身是一个前端程序,它为真正的编译器做准备,然后如果被要求(如果你不说不)将调用汇编器和链接器。
cat so.i
# 1 "so.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "so.c"
unsigned int x;
unsigned int y = 5;
const unsigned int z = 7;
unsigned int fun ( unsigned int a )
{
return(a+1);
}
Run Code Online (Sandbox Code Playgroud)
所以在这一点上定义和包含被处理,它的一个大文件被发送到编译器。
编译器完成它的工作并将其转换为汇编语言
cat so.s
.cpu arm7tdmi
.eabi_attribute 20, 1
.eabi_attribute 21, 1
.eabi_attribute 23, 3
.eabi_attribute 24, 1
.eabi_attribute 25, 1
.eabi_attribute 26, 1
.eabi_attribute 30, 2
.eabi_attribute 34, 0
.eabi_attribute 18, 4
.file "so.c"
.text
.align 2
.global fun
.arch armv4t
.syntax unified
.arm
.fpu softvfp
.type fun, %function
fun:
@ Function supports interworking.
@ args = 0, pretend = 0, frame = 0
@ frame_needed = 0, uses_anonymous_args = 0
@ link register save eliminated.
add r0, r0, #1
bx lr
.size fun, .-fun
.global z
.global y
.comm x,4,4
.section .rodata
.align 2
.type z, %object
.size z, 4
z:
.word 7
.data
.align 2
.type y, %object
.size y, 4
y:
.word 5
.ident "GCC: (GNU) 9.3.0"
Run Code Online (Sandbox Code Playgroud)
然后将其放入目标文件中,在本例中为 binutils、linux default 等
file so.o
so.o: ELF 32-bit LSB relocatable, ARM, EABI5 version 1 (SYSV), not stripped
Run Code Online (Sandbox Code Playgroud)
它使用 elf 文件格式,易于查找信息,易于编写要解析的程序等。
我可以反汇编它,请注意,因为我正在使用反汇编器,它会尝试反汇编所有内容,即使它不是机器代码,坚持使用 32 位 arm 的东西它可以通过它,当有真正的指令时,它们会显示(对齐和不是此处使用的可变长度,因此您可以线性地反汇编,而使用可变长度指令集则不能,并且希望成功(例如 x86),您需要按执行顺序反汇编,然后由于性质的原因,您经常会错过一些程序)
arm-none-eabi-objdump -D so.o
so.o: file format elf32-littlearm
Disassembly of section .text:
00000000 <fun>:
0: e2800001 add r0, r0, #1
4: e12fff1e bx lr
Disassembly of section .data:
00000000 <y>:
0: 00000005 andeq r0, r0, r5
Disassembly of section .rodata:
00000000 <z>:
0: 00000007 andeq r0, r0, r7
Disassembly of section .comment:
00000000 <.comment>:
0: 43434700 movtmi r4, #14080 ; 0x3700
4: 4728203a ; <UNDEFINED> instruction: 0x4728203a
8: 2029554e eorcs r5, r9, lr, asr #10
c: 2e332e39 mrccs 14, 1, r2, cr3, cr9, {1}
10: Address 0x0000000000000010 is out of bounds.
Disassembly of section .ARM.attributes:
00000000 <.ARM.attributes>:
0: 00002941 andeq r2, r0, r1, asr #18
4: 61656100 cmnvs r5, r0, lsl #2
8: 01006962 tsteq r0, r2, ror #18
c: 0000001f andeq r0, r0, pc, lsl r0
10: 00543405 subseq r3, r4, r5, lsl #8
14: 01080206 tsteq r8, r6, lsl #4
18: 04120109 ldreq r0, [r2], #-265 ; 0xfffffef7
1c: 01150114 tsteq r5, r4, lsl r1
20: 01180317 tsteq r8, r7, lsl r3
24: 011a0119 tsteq r10, r9, lsl r1
28: Address 0x0000000000000028 is out of bounds.
Run Code Online (Sandbox Code Playgroud)
是的,该工具在其中放置了额外的东西,但请注意主要是我创建的。一些代码,一些初始化的读/写数据,一些初始化的读/写数据和一些初始化的只读数据。工具链作者可以使用他们想要的任何名称,他们甚至不必使用术语部分。但是从几十年的历史和通信和术语来看,.text 通常用于代码(如只读机器代码和相关数据),.bss 用于清零读/写数据,尽管我见过其他名称,.data 用于初始化读/写data 和此工具的这一代 .rodata 用于只读初始化数据(技术上可以在 .text 中)
请注意,它们的地址都为零。他们还没有联系起来。
现在这很丑陋,但为了避免添加更多代码,如果该工具允许我这样做,让我们链接它以制作一个完全无法使用的二进制文件(没有引导程序等):
arm-none-eabi-ld -Ttext=0x1000 -Tdata=0x2000 so.o -o so.elf
arm-none-eabi-ld: warning: cannot find entry symbol _start; defaulting to 0000000000001000
arm-none-eabi-objdump -D so.elf
so.elf: file format elf32-littlearm
Disassembly of section .text:
00001000 <fun>:
1000: e2800001 add r0, r0, #1
1004: e12fff1e bx lr
Disassembly of section .data:
00002000 <y>:
2000: 00000005 andeq r0, r0, r5
Disassembly of section .rodata:
00001008 <z>:
1008: 00000007 andeq r0, r0, r7
Disassembly of section .bss:
00002004 <x>:
2004: 00000000 andeq r0, r0, r0
Run Code Online (Sandbox Code Playgroud)
现在它已链接。只读项 .text 和 .rodata 按照文件中的顺序进入 .text 地址空间。读/写项目按照文件中找到的顺序进入 .data 地址空间。
是的,对象中的 .bss 在哪里?它在那里,它没有作为对象一部分的字节形式的实际数据,而是具有名称和大小,并且它是 .bss。无论出于何种原因,该工具确实从链接的二进制文件中显示了它。
所以回到术语二进制。so.elf 二进制文件具有构成程序的内存中的字节,还有文件格式基础结构和符号表,使反汇编和调试更容易以及其他内容。Elf 是一种灵活的文件格式,gnu 可以使用它,您会得到一个结果,其他工具或工具版本可以使用它并拥有不同的文件。很明显,两个编译器可以从同一个源程序生成不同的机器代码,不仅仅是因为优化,工作是用目标语言制作一个函数式程序,函数式是编译器/工具作者的意见。
内存图像类型文件怎么样:
arm-none-eabi-objcopy so.elf so.bin -O binary
hexdump -C so.bin
00000000 01 00 80 e2 1e ff 2f e1 07 00 00 00 00 00 00 00 |....../.........|
00000010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
00001000 05 00 00 00 |....|
00001004
Run Code Online (Sandbox Code Playgroud)
现在 objcopy 工具的工作原理是它从第一个定义的可加载项或任何您想使用字节的术语开始,并以最后一个结束,并使用(零)填充使文件大小匹配,以便内存图像从地址匹配看法。星号表示基本上为 0 填充。因为我们从 .text 的 0x1000 和 .data 的 0x2000 开始,但是这个文件的第一个字节(偏移量 0)是 .text 的开头,0x1000 字节之后是文件中的偏移量 0x1000 但我们知道它在内存中到达 0x2000是读/写的东西。另请注意,bss 零不在输出中。引导程序预计将这些归零。
没有像这个文件中的数据在内存中的位置等信息。如果你想一想,如果我在我定义的一个部分有一个字节到 0x00000000 而在我定义的一个部分有一个字节到 0x80000000并输出这个文件,是的,即使只有两个有用的相关信息字节,它也是一个 0x80000001 字节的文件。一个 2GB 的文件来保存两个字节。这就是为什么在整理好链接描述文件和工具之前不想输出这种文件格式的原因。
相同的数据和另外两种同样老派的格式,有一点英特尔与摩托罗拉的历史
arm-none-eabi-objcopy so.elf so.hex -O ihex
cat so.hex
:08100000010080E21EFF2FE158
:0410080007000000DD
:0420000005000000D7
:0400000300001000E9
:00000001FF
arm-none-eabi-objcopy so.elf so.srec -O srec
cat so.srec
S00A0000736F2E7372656338
S10B1000010080E21EFF2FE154
S107100807000000D9
S107200005000000D3
S9031000EC
Run Code Online (Sandbox Code Playgroud)
现在这些包含相关字节,加上地址,但没有太多其他信息,每个数据字节需要超过两个字节,但与带有填充的巨大文件相比,值得权衡。这两种格式今天都在使用,不像过去那样多,但仍然存在。
无数其他二进制文件格式和像 objdump 这样的工具有一个不错的格式列表,它可以生成以及其他链接器和/或工具。
与所有这些相关的是,存在某种形式的二进制文件格式,其中包含我们运行程序所需的字节。
您可能会问什么格式和什么地址……这是操作系统或系统设计的一部分。在 Windows 的情况下,有特定的文件格式和可能由 Windows 操作系统支持的这些格式的变体,您正在使用的特定版本。Windows 已经确定了地址空间的样子。像这样的操作系统利用 MMU 来虚拟化地址和保护。拥有虚拟地址空间意味着每个程序都可以生活在同一个空间中。所有程序都可以有一个基于零的地址,例如......
测试.c
int main ( void )
{
return 1;
}
Run Code Online (Sandbox Code Playgroud)
你好ç
int main ( void )
{
return 2;
}
gcc test.c -o test
objdump -D test
Disassembly of section .text:
00000000004003e0 <_start>:
4003e0: 31 ed xor %ebp,%ebp
4003e2: 49 89 d1 mov %rdx,%r9
4003e5: 5e pop %rsi
...
gcc hello.c -o hello
objdump -D hello
Disassembly of section .text:
00000000004003e0 <_start>:
4003e0: 31 ed xor %ebp,%ebp
4003e2: 49 89 d1 mov %rdx,%r9
Run Code Online (Sandbox Code Playgroud)
同一个地址,他们怎么可能不坐在彼此的顶部?没有虚拟机。请注意,这是在特定日期为特定 Linux 构建的,等等。当为此目标/平台构建编译器时,工具链具有用于此平台的默认链接器脚本(注意我没有指定如何链接)。
arm-none-eabi-gcc -O2 test.c -c -o test.o
arm-none-eabi-ld test.o -o test.elf
arm-none-eabi-ld: warning: cannot find entry symbol _start; defaulting to 0000000000008000
arm-none-eabi-objdump -D test.elf
test.elf: file format elf32-littlearm
Disassembly of section .text:
00008000 <main>:
8000: e3a00001 mov r0, #1
8004: e12fff1e bx lr
Run Code Online (Sandbox Code Playgroud)
相同的源代码,相同的编译器,为不同的目标和系统构建不同的地址。
所以对于 Windows,肯定会有支持的二进制格式的规则和可以使用的地址空间的规则,如何在文件中定义这些空间。
然后操作系统启动器读取二进制文件并将可加载项放入内存中的那些地址(在操作系统为此特定程序创建的虚拟空间中)是一件简单的事情。 loader 对您来说是零 bss,因为信息在那里。低级程序员需要知道可能处理归零 .bss 与否。
如果没有,您将看到并可能需要创建一个解决方案,不幸的是,这是您深入了解工具特定项目的地方。虽然 C 可能有些标准化,但工具/作者没有或至少是标准化的工具特定的东西,但没有理由假设这些东西与其他工具交叉。
.globl _start
_start:
ldr sp,sp_init
bl fun
b .
.word __bss_start__
.word __bss_end__
sp_init:
.word 0x8000
Run Code Online (Sandbox Code Playgroud)
关于汇编语言的一切都是特定于工具的,出于理智原因的助记符无疑类似于 ip/处理器供应商文档,该文档使用他们付费开发的工具使用的语法。但是除了汇编语言完全由工具而不是目标定义之外,x86 因为它的年龄和其他事情真的很糟糕,这不是英特尔与 AT&T 的事情,只是一般。Gnu 汇编器是众所周知的,因为我认为可能故意不使语言与其他汇编语言兼容。以上是arm的gnu汇编程序。
使用上面的 fun() 函数,C 说它应该是 main() 但该工具不在乎我已经在这里输入了足够的内容。
添加一个简单的基于 ram 的链接器脚本
MEMORY
{
ram : ORIGIN = 0x1000, LENGTH = 0x1000
}
SECTIONS
{
.text : { *(.text*) } > ram
.rodata : { *(.rodata*) } > ram
.bss : {
__bss_start__ = .;
*(.bss*)
} > ram
__bss_end__ = .;
}
Run Code Online (Sandbox Code Playgroud)
建立这一切
arm-none-eabi-as start.s -o start.o
arm-none-eabi-gcc -O2 -c so.c -o so.o
arm-none-eabi-ld -T sram.ld start.o so.o -o so.elf
Run Code Online (Sandbox Code Playgroud)
检查
arm-none-eabi-nm so.elf
0000102c B __bss_end__
00001028 B __bss_start__
00001018 T fun
00001014 t sp_init
00001000 T _start
00001028 B x
00001024 D y
00001020 R z
arm-none-eabi-objdump -D so.elf
so.elf: file format elf32-littlearm
Disassembly of section .text:
00001000 <_start>:
1000: e59fd00c ldr sp, [pc, #12] ; 1014 <sp_init>
1004: eb000003 bl 1018 <fun>
1008: eafffffe b 1008 <_start+0x8>
100c: 00001028 andeq r1, r0, r8, lsr #32
1010: 0000102c andeq r1, r0, r12, lsr #32
00001014 <sp_init>:
1014: 00008000 andeq r8, r0, r0
00001018 <fun>:
1018: e2800001 add r0, r0, #1
101c: e12fff1e bx lr
Disassembly of section .rodata:
00001020 <z>:
1020: 00000007 andeq r0, r0, r7
Disassembly of section .data:
00001024 <y>:
1024: 00000005 andeq r0, r0, r5
Disassembly of section .bss:
00001028 <x>:
1028: 00000000 andeq r0, r0, r0
Run Code Online (Sandbox Code Playgroud)
因此,现在可以根据开始和结束地址向引导程序添加内存归零循环(不要使用 C/memset,您不会在 asm 中编写引导程序时产生鸡和蛋问题)。
幸运还是不幸,因为链接器脚本是特定于工具的,而汇编语言是特定于工具的,如果您让工具为您完成工作,它们需要协同工作(这样做的明智方法,找出 .bss 的位置很有趣,否则)。
这可以在操作系统上完成,但是当您进入必须在非易失性存储(闪存)上的微控制器时,可能会从其他地方下载一个(例如您的鼠标固件有时,有时是键盘等)到 ram,假设 flash,那么你如何处理 .data??
MEMORY
{
rom : ORIGIN = 0x0000, LENGTH = 0x1000
ram : ORIGIN = 0x1000, LENGTH = 0x1000
}
SECTIONS
{
.text : { *(.text*) } > rom
.rodata : { *(.rodata*) } > rom
.data : {
*(.data*)
} > ram AT > rom
.bss : {
__bss_start__ = .;
*(.bss*)
} > ram
__bss_end__ = .;
}
Run Code Online (Sandbox Code Playgroud)
使用 gnu ld 这基本上是说 .data 的家在 ram 中,但输出二进制格式会将它放在 flash/rom 中
so.elf so.srec -O srec
cat so.srec
S00A0000736F2E7372656338
S11300000CD09FE5030000EBFEFFFFEA04100000A4
S11300100810000000800000010080E21EFF2FE1B4
S107002007000000D1 <- z variable at address 0020
S107002405000000CF <- y variable at 0024
S9030000FC
Run Code Online (Sandbox Code Playgroud)
并且您必须更多地使用链接器脚本才能让工具告诉您 ram 和 flash 的起始地址和结束地址或长度。然后在引导程序(asm 不是 C)中添加代码以将 .data 从闪存复制到内存。
另请注意您的许多问题中的另一个。
.word __bss_start__
.word __bss_end__
sp_init:
.word 0x8000
Run Code Online (Sandbox Code Playgroud)
这些项目是技术数据。但它们首先存在于 .text 中,因为它们是在假定为 .text 的代码中定义的(我不需要在 asm 中说明,但可以这样做)。您也将在 x86 中看到这一点,但对于固定长度,如 arm、mips、risc-v 等,您不能在指令本身中放置任何旧的立即数/常量/链接值,而是将其放在“池”附近并做一个 pc 相对读取来获取它。您也会在链接外部时看到这一点:
extern unsigned int x;
int main ( void )
{
return x;
}
arm-none-eabi-gcc -O2 -c test.c -o test.o
arm-none-eabi-objdump -D test.o
test.o: file format elf32-littlearm
Disassembly of section .text.startup:
00000000 <main>:
0: e59f3004 ldr r3, [pc, #4] ; c <main+0xc>
4: e5930000 ldr r0, [r3]
8: e12fff1e bx lr
c: 00000000 andeq r0, r0, r0 <--- the code gets the address of the
Run Code Online (Sandbox Code Playgroud)
变量从这里开始,然后从内存中读取它
一旦链接
Disassembly of section .text:
00008000 <main>:
8000: e59f3004 ldr r3, [pc, #4] ; 800c <main+0xc>
8004: e5930000 ldr r0, [r3]
8008: e12fff1e bx lr
800c: 00018010 andeq r8, r1, r0, lsl r0
Disassembly of section .data:
00018010 <x>:
18010: 00000005 andeq r0, r0, r5
Run Code Online (Sandbox Code Playgroud)
对于 x86
gcc -c -O2 test.c -o test.o
dwelch-desktop so # objdump -D test.o
test.o: file format elf64-x86-64
Disassembly of section .text.startup:
0000000000000000 <main>:
0: 8b 05 00 00 00 00 mov 0x0(%rip),%eax # 6 <main+0x6>
6: c3 retq
00000000004003e0 <main>:
4003e0: 8b 05 4a 0c 20 00 mov 0x200c4a(%rip),%eax # 601030 <x>
4003e6: c3 retq
Run Code Online (Sandbox Code Playgroud)
如果你眯着眼睛,真的不一样吗?附近有数据供处理器读取以加载到寄存器中或使用。无论哪种方式,由于指令集的性质,链接器会修改指令或附近的池数据或两者。
最后一个:
arm-none-eabi-gcc -S test.c
cat test.s
.cpu arm7tdmi
.eabi_attribute 20, 1
.eabi_attribute 21, 1
.eabi_attribute 23, 3
.eabi_attribute 24, 1
.eabi_attribute 25, 1
.eabi_attribute 26, 1
.eabi_attribute 30, 6
.eabi_attribute 34, 0
.eabi_attribute 18, 4
.file "test.c"
.text
.align 2
.global main
.arch armv4t
.syntax unified
.arm
.fpu softvfp
.type main, %function
main:
@ Function supports interworking.
@ args = 0, pretend = 0, frame = 0
@ frame_needed = 1, uses_anonymous_args = 0
@ link register save eliminated.
str fp, [sp, #-4]!
add fp, sp, #0
ldr r3, .L3
ldr r3, [r3]
mov r0, r3
add sp, fp, #0
@ sp needed
ldr fp, [sp], #4
bx lr
.L4:
.align 2
.L3:
.word x
.size main, .-main
.ident "GCC: (GNU) 9.3.0"
Run Code Online (Sandbox Code Playgroud)
所以你能看到汇编语言吗,是的,有些工具可以让你保存中间文件和/或让你在编译时生成文件的汇编输出。
你能在代码中有数据吗,是的,在 .text 区域中有数据值有时间和原因,而不仅仅是目标特定的,你会因为各种原因看到这一点,并且一些工具链将只读数据放在那里。
现代操作系统使用的文件格式有很多,它们不仅具有定义构成机器代码和数据值的字节的功能,而且还包括符号和其他调试信息。
程序的文件格式和内存空间是特定于操作系统的,而不是特定于语言的,甚至是特定于目标的(尽管目标计算机完全相同,但预计同一台笔记本电脑上的 Linux、Windows、MacOS 不会具有相同的规则)。该平台的本机工具链具有默认链接器脚本以及为该目标构建可用/工作程序所需的任何其他信息。包括支持的文件格式。
机器代码和数据项可以以不同的方式以不同的文件格式表示,目标系统的操作系统或加载程序是否可以使用该格式取决于目标系统。
程序有错误和细微差别。文件格式有版本和不一致之处,您可能会发现某些 elf 文件格式阅读器只是在输入可在某些系统上运行的完美 elf 文件时发现它不起作用或打印出奇怪的东西。为什么要设置一些标志?也许这些字节被重新使用,或者标志被重新利用,或者数据结构发生了变化,或者工具以不同的方式或以非标准方式使用它(想想 mov 20h,ax)而另一个不兼容的工具无法理解或很幸运并且足够接近。
在 Stack Overflow 上问“为什么”问题不是很有用,找到编写该东西的人的几率非常低,询问您获得该工具的地方并跟进的几率更高,希望该人还活着并愿意被打扰。并且 99.999(很多 9)% 没有一套全球性的敬虔规则来说明这件事是在/为之编写的。一般是一些家伙只是觉得这就是他们做他们所做的事情的原因,没有真正的原因,懒惰,一个错误,故意试图破坏别人的工具。一直到在特定日期在特定房间对它进行投票的大型委员会,这就是原因(我们知道当我们由委员会设计或尝试编写没有人遵守的规范时会得到什么)。
我知道您使用的是 Windows,而我手头没有 Windows 机器并且使用的是 Linux。但是 gnu/binutils 和 clang/llvm 工具很容易获得,并且拥有丰富的工具集,如 readelf、nm、objdump 等。这有助于检查事物,一个好的工具至少在内部为开发人员提供他们可以将工具的输出调试到一定的质量水平。gnu 人制作了工具并使每个人都可以使用它们,虽然整理它们及其功能需要时间,但它们对于您试图理解的事物非常强大。
你不会找到一个好的 x86 反汇编器,它们都是垃圾,仅仅是因为野兽的本性。它是一个可变长度的指令集,因此根据定义,除非您正在执行,否则您无法正确对其进行排序。您必须从已知的良好入口点按执行顺序进行反汇编,以便有一半的机会,然后由于各种原因,您无法以这种方式看到代码路径(例如跳转表,或 dll 等文件)。最好的解决方案是拥有一个非常准确/完美的模拟器/模拟器并运行代码并执行您需要做的所有操作/旋转以使其覆盖所有代码路径,并让该工具记录来自数据的指令以及每个位于或每个线性部分没有分支。
这样做的好处是,今天很多代码都是使用不试图隐藏任何东西的工具编译的。在过去,由于各种原因,您会看到手写的 asm 故意试图防止反汇编或由于其他因素(在贸易展前一天手动编辑视频游戏的二进制 rom 图像,去反汇编一些经典的 rom) .
mov r0,#0
cmp r0,#0
jz somewhere
.word 0x12345678
Run Code Online (Sandbox Code Playgroud)
反汇编程序不会解决这个问题,有些人可能会为此添加一个案例
mov r0,#0
nop
nop
xor r0,#1
nop
nop
xor r0,#3
xor r0,#2
cmp r0,#0
jz somewhere
.word 0x12345678
Run Code Online (Sandbox Code Playgroud)
并且它认为数据是一条指令,对于反汇编程序来说很难解决一个像样的可变长度的可变长度至少会检测到指令的非操作码部分分支到和/或指令的操作码部分显示的冲突稍后作为其他指令中的附加字节。该工具无法解决人类必须解决的问题。
即使使用 arm 和 mips 并具有 32 位和 16 位指令、具有可变大小指令的 risc-v 等...
gnu 的反汇编器经常会被 x86 绊倒。
归档时间: |
|
查看次数: |
1106 次 |
最近记录: |