返回介绍

6.5 Global Variables

发布于 2025-02-22 14:00:43 字数 7018 浏览 0 评论 0 收藏 0

如果之前的例子中的 x 变量不再是本地变量而是全局变量呢?那么就有机会接触任何指针,不仅仅是函数体,全局变量被认为 anti-pattern(通常被认为是一个不好的习惯),但是为了试验,我们可以这样做。

#!cpp
#include <stdio.h>
int x;
int main()
{
    printf ("Enter X:
");
    scanf ("%d", &x);
    printf ("You entered %d...
", x);
    return 0;
};

6.5.1 MSVC: x86

#!bash
_DATA       SEGMENT
COMM        _x:DWORD
$SG2456     DB      ’Enter X:’, 0aH, 00H
$SG2457     DB      ’%d’, 00H
$SG2458     DB      ’You entered %d...’, 0aH, 00H
_DATA   ENDS
PUBLIC  _main
EXTRN   _scanf:PROC
EXTRN   _printf:PROC
; Function compile flags: /Odtp
_TEXT   SEGMENT
_main   PROC
    push    ebp
    mov     ebp, esp
    push    OFFSET $SG2456
    call    _printf
    add     esp, 4
    push    OFFSET _x
    push    OFFSET $SG2457
    call    _scanf
    add     esp, 8
    mov     eax, DWORD PTR _x
    push    eax
    push    OFFSET $SG2458
    call    _printf
    add     esp, 8
    xor     eax, eax
    pop     ebp
    ret     0
_main ENDP
_TEXT ENDS

现在 x 变量被定义为在_DATA 部分,局部堆栈不允许再分配任何内存,除了直接访问内存所有通过栈的访问都不被允许。在执行的文件中全局变量还未初始化(实际上,我们为什么要在执行文件中为未初始化的变量分配一块?) 但是当访问这里时,系统会在这里分配一块 0 值。

现在让我们明白的来分配变量吧"

#!bash
int x=10; // default value

我们得到:

_DATA   SEGMENT
_x      DD      0aH
...

这里我们看见一个双字节的值 0xA(DD 表示双字节 = 32bit)

如果你在 IDA 中打开 compiled.exe,你会发现 x 变量被放置在_DATA 块的开始处,接着你就会看见文本字符串。

如果你在 IDA 中打开之前例子中的 compiled.exe 中 X 变量没有定义的地方,你就会看见像这样的东西:

#!bash
.data:0040FA80 _x               dd ?        ; DATA XREF: _main+10
.data:0040FA80                              ; _main+22
.data:0040FA84 dword_40FA84     dd ?        ; DATA XREF: _memset+1E
.data:0040FA84                              ; unknown_libname_1+28
.data:0040FA88 dword_40FA88     dd ?        ; DATA XREF: ___sbh_find_block+5
.data:0040FA88                              ; ___sbh_free_block+2BC
.data:0040FA8C ; LPVOID lpMem
.data:0040FA8C lpMem            dd ?        ; DATA XREF: ___sbh_find_block+B
.data:0040FA8C                              ; ___sbh_free_block+2CA
.data:0040FA90 dword_40FA90     dd ?        ; DATA XREF: _V6_HeapAlloc+13
.data:0040FA90                              ; __calloc_impl+72
.data:0040FA94 dword_40FA94     dd ?        ; DATA XREF: ___sbh_free_block+2FE

_x 替换了?其它变量也并未要求初始化,这也就是说在载入 exe 至内存后,在这里有一块针对所有变量的空间,并且还有一些随机的垃圾数据。但在在 exe 中这些没有初始化的变量并不影响什么,比如它适合大数组。

6.5.2 MSVC: x86 + OllyDbg

到这里事情就变得简单了(见表 6.5),变量都在 data 部分,顺便说一句,在 PUSH 指令后,压入 x 的地址,被执行后,地址将会在栈中显示,那么右击元组数据,点击"Fllow in dump",然后变量就会在左侧内存窗口显示。

在命令行窗口中输入 123 后,这里就会显示 0x7B

但是为什么第一个字节是 7B?合理的猜测,这里会有一组 00 00 7B,被称为是字节顺序,然后在 x86 中使用的是小端,也就是说低位数据先写,高位数据后写。

不一会,这里的 32-bit 值就会载入到 EAX 中,然后被传递给 printf().

X 变量地址是 0xDC3390.在 OllyDbg 中我们看进程内存映射(Alt-M),然后发现这个地在 PE 文件.data 结构处。见表 6.6

enter image description here

表 6.5 OllyDbg: scanf() 执行后

enter image description here

表 6.6: OllyDbg 进程内存映射

6.5.3 GCC: x86

这和 linux 中几乎是一样的,除了 segment 的名称和属性:未初始化变量被放置在_bss 部分。

在 ELF 文件格式中,这部分数据有这样的属性:

; Segment type: Uninitialized
; Segment permissions: Read/Write

如果静态的分配一个值,比如 10,它将会被放在_data 部分,这部分有下面的属性:

; Segment type: Pure data
; Segment permissions: Read/Write

6.5.4 MSVC: x64

#!bash
_DATA       SEGMENT
COMM        x:DWORD
$SG2924     DB      ’Enter X:’, 0aH, 00H
$SG2925     DB      ’%d’, 00H
$SG2926     DB      ’You entered %d...’, 0aH, 00H
_DATA       ENDS

_TEXT       SEGMENT
main        PROC
$LN3:
            sub     rsp, 40
            lea     rcx, OFFSET FLAT:$SG2924 ; ’Enter X:’
            call    printf
            lea     rdx, OFFSET FLAT:x
            lea     rcx, OFFSET FLAT:$SG2925 ; ’%d’
            call    scanf
            mov     edx, DWORD PTR x
            lea     rcx, OFFSET FLAT:$SG2926 ; ’You entered %d...’
            call    printf
            ; return 0
            xor     eax, eax
            add     rsp, 40
            ret     0
main ENDP
_TEXT ENDS

几乎和 x86 中的代码是一样的,发现 x 变量的地址传递给 scanf() 用的是 LEA 指令,尽管第二处传递给 printf() 变量时用的是 MOV 指令,"DWORD PTR"——是汇编语言中的一部分(和机器码没有联系)。这就表示变量数据类型是 32-bit,于是 MOV 指令就被编码了。

6.5.5 ARM:Optimizing Keil + thumb mode

#!bash
.text:00000000 ; Segment type: Pure code
.text:00000000          AREA .text, CODE
...
.text:00000000 main
.text:00000000                  PUSH    {R4,LR}
.text:00000002                  ADR     R0, aEnterX         ; "Enter X:
"
.text:00000004                  BL      __2printf
.text:00000008                  LDR     R1, =x
.text:0000000A                  ADR     R0, aD              ; "%d"
.text:0000000C                  BL      __0scanf
.text:00000010                  LDR     R0, =x
.text:00000012                  LDR     R1, [R0]
.text:00000014                  ADR     R0, aYouEnteredD___ ; "You entered %d...
"
.text:00000016                  BL      __2printf
.text:0000001A                  MOVS    R0, #0
.text:0000001C                  POP     {R4,PC}
...
.text:00000020 aEnterX          DCB     "Enter X:",0xA,0    ; DATA XREF: main+2
.text:0000002A                  DCB     0
.text:0000002B                  DCB     0
.text:0000002C off_2C           DCD x                       ; DATA XREF: main+8
.text:0000002C                                      ; main+10
.text:00000030 aD               DCB     "%d",0              ; DATA XREF: main+A
.text:00000033                  DCB 0
.text:00000034 aYouEnteredD___  DCB "You entered %d...",0xA,0 ; DATA XREF: main+14
.text:00000047                  DCB 0
.text:00000047 ; .text          ends
.text:00000047
...
.data:00000048 ; Segment type:  Pure data
.data:00000048                  AREA .data, DATA
.data:00000048                  ; ORG 0x48
.data:00000048                  EXPORT x
.data:00000048 x                DCD 0xA                     ; DATA XREF: main+8
.data:00000048                                              ; main+10
.data:00000048                                              ; .data ends

那么,现在 x 变量以某种方式变为全局的,现在被放置在另一个部分中。命名为 data 块(.data)。有人可能会问,为什么文本字符串被放在了代码块(.text),而且 x 可以被放在这?因为这是变量,而且根据它的定义,它可以变化,也有可能会频繁变化,不频繁变化的代码块可以被放置在 ROM 中,变化的变量在 RAM 中,当有 ROM 时在 RAM 中储存不变的变量是不利于节约资源的。

此外,RAM 中数据部分常量必须在之前初始化,因为在 RAM 使用后,很明显,将会包含杂乱的信息。

继续向前,我们可以看到,在代码片段,有个指针指向 X 变量(0ff_2C)。然后所有关于变量的操作都是通过这个指针。这也是 x 变量可以被放在远离这里地方的原因。所以他的地址一定被存在离这很近的地方。LDR 指令在 thumb 模式下只可访问指向地址在 1020bytes 内的数据。同样的指令在 ARM 模式下——范围就达到了 4095bytes,也就是 x 变量地址一定要在这附近的原因。因为没法保证链接时会把这个变量放在附近。

另外,如果变量以 const 声明,Keil 编译环境下则会将变量放在.constdata 部分,大概从那以后,链接时就可以把这部分和代码块放在 ROM 里了。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文