【问题标题】:Why do I get incorrect results "ffff..." when inspecting the bytes that make up a compiled function stored in memory?函数是如何存储在内存中的?
【发布时间】:2012-12-15 20:44:33
【问题描述】:

我一直在深入研究 Linux 和 C,我很好奇函数是如何存储在内存中的。 我有以下功能:

void test(){
    printf( "test\n" );
}

足够简单。当我在具有此功能的可执行文件上运行 objdump 时,我得到以下信息:

08048464 <test>:
 8048464:       55                      push   %ebp
 8048465:       89 e5                   mov    %esp,%ebp
 8048467:       83 ec 18                sub    $0x18,%esp
 804846a:       b8 20 86 04 08          mov    $0x8048620,%eax
 804846f:       89 04 24                mov    %eax,(%esp)
 8048472:       e8 11 ff ff ff          call   8048388 <printf@plt>
 8048477:       c9                      leave
 8048478:       c3                      ret

这一切看起来都不错。 有趣的是当我运行以下代码时:

int main( void ) {
    char data[20];
    int i;    
    memset( data, 0, sizeof( data ) );
    memcpy( data, test, 20 * sizeof( char ) );
    for( i = 0; i < 20; ++i ) {
        printf( "%x\n", data[i] );
    }
    return 0;
}

我得到以下信息(不正确):

55
ffffff89
ffffffe5
ffffff83
ffffffec
18
ffffffc7
4
24
10
ffffff86
4
8
ffffffe8
22
ffffffff
ffffffff
ffffffff
ffffffc9
ffffffc3

如果我选择忽略 memset(data, 0, sizeof(data));行,则最右边的字节是正确的,但其中一些仍然具有前导 1。

有没有人解释一下原因

A) 使用 memset 清除我的数组会导致函数表示不正确(编辑:不准确),并且

解决方案:是由于使用了 memset(data, 0, sizeof(data)),而不是 memset(data, 0, 20 * sizeof(unsigned char))。内存没有完全设置,因为它只查看指针的大小而不是整个数组的大小。

B) 这个字节存储在内存中是什么?整数?字符?我不太明白这里发生了什么。 (澄清:我将使用什么类型的指针来遍历内存中的此类数据?)

解决方案:我很笨。我忘记了 unsigned 关键字,这就是整个问题的来源:(

任何帮助将不胜感激 - 我在四处搜索时找不到任何东西。

尼尔

PS:我的直接想法是,这是由于 x86 的指令不以字节或半字节边界结束。但这没有多大意义,也不应该造成任何问题。

感谢 Will 指出我的 char 类型错误。它应该是无符号字符。不过,我仍然对如何访问单个字节感到好奇。

【问题讨论】:

    标签: c linux function memory objdump


    【解决方案1】:

    我相信您的chars 正在符号扩展到整数的宽度。通过在打印时显式转换值,您可能会得到更接近您想要的结果。

    【讨论】:

    • 我不相信对于不表现出相同行为的偶尔值(即 55、4、18 等)会出现这种情况。如果它们都是符号扩展的,那么我相信这将是解决方案。
    • 这些值的高位为零。扩展零位有点不可见。有问题的有一个高位。
    • 相信你看的是符号扩展数据的十六进制。如果值为0x00000055,则printf 放入55。如果是0xFFFFFF89,那么它会打印完整的值。如果您希望它确保打印所有前导 0,请使用 "%0x"
    • 哇,多么愚蠢的疏忽。我忘记了未签名的关键字-_-'
    【解决方案2】:

    这是您尝试执行的代码的一个更简单的例子:

    int main( void ) {
        unsigned char *data = (unsigned char *)test;
        int i;    
        for( i = 0; i < 20; ++i ) {
            printf( "%02x\n", data[i] );
        }
        return 0;
    }
    

    我所做的更改是删除多余的缓冲区,而不是使用指针进行测试,使用 unsigned char 代替 char,并将 printf 更改为使用“%02x”,以便它始终打印两个字符 [它不会' t 修复以 ffffff89 左右出现的“负”数字 - 这是用数据指针上的 unsigned 修复的。

    x86 中的所有指令都以字节边界结束,编译器通常会插入额外的“填充指令”以确保分支目标与 4、8 或 16 字节边界对齐以提高效率。

    【讨论】:

      【解决方案3】:

      问题在于您要打印的代码。

      从数据数组中加载一个字节。 (一个字节 == 一个字符)

      字节被转换为“int”,因为这是编译器知道“printf”想要的。为此,它将字节扩展为 32 位双字。这就是打印为十六进制的内容。 (这意味着高位为 1 的字节将转换为 32 位值,其中 8-31 位全部设置。这就是您看到的 ffffffxx 值。)

      在这种情况下我要做的是自己转换它:

       printf( "%x\n", ((int)data[i] && 0xFF) );
      

      然后它将正确打印。 (如果您正在加载 16 位值,您会使用 0xffff 进行 AND。)

      【讨论】:

        【解决方案4】:

        答案 B) 字节作为字节存储在内存中。内存位置恰好包含 1 个字节的内存位置。(一个字节为unsigned char

        提示:选择一本关于计算机组织的好书(我最喜欢的是 Carl Hamachar 的书,并且非常了解内存的内部表示方式)

        在您的代码中:

        memset( data, 0, sizeof( data ) );// must be memset(data,0,20);
        memcpy( data, test, 20 * sizeof( char ) ); 
        for( i = 0; i < 20; ++i ) {
            printf( "%x\n", data[i] );// prints a CHARACTER up-casted to an INTEGER in HEX representation, hence the extra `0xFFFFFF`
        }
        

        【讨论】:

        • a) memset 中的优化不应导致调用 memcpy 来制作数据的非精确副本。 b) 如何从 c 访问它?最接近字节类型的是 unsigned char
        【解决方案5】:

        打印看起来很奇怪,因为您正在打印带符号的值,因此它们正在被符号扩展。

        但是,打印的功能也略有不同。看起来不是用字符串的地址加载 EAX 并将其填充到堆栈中,而是直接存储地址。

        push        ebp  
        mov         ebp,esp  
        sub         esp,18h  
        mov         dword ptr [esp],8048610h  
        call        <printf>  
        leave  
        ret  
        

        至于为什么当您在代码的其他地方进行看似良性的更改时它会发生变化 - 好吧,这是允许的。这就是为什么最好不要依赖未定义的行为。

        【讨论】:

        • 它加载到eax然后放入栈上保留空间的东西是字符串'test\n' (0x8048620)的地址
        猜你喜欢
        • 2014-10-09
        • 2012-05-28
        • 2018-04-20
        • 2018-10-04
        • 2015-08-05
        • 2012-07-03
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多