为什么exit函数在run前后会发生变化?

内核编译和嵌入式产品的设计与开发
回复
vfdff
帖子: 21
注册时间: 2008-11-01 15:22

为什么exit函数在run前后会发生变化?

#1

帖子 vfdff » 2010-11-14 11:35

测试代码:

1. #include <malloc.h>
2. #include <stdio.h>
3. #include <stdlib.h>
4. #include <string.h>
5. #include <asm/page.h>
6. #include <sys/mman.h>
7.
8. int main(int argc,char** argv)
9. {
10. printf("\ntest generic function 11\n");
11.
12. exit(1);
13.
14. printf("\ntest generic function 31\n");
15.
16. return 0;
17. }


复制代码
(gdb) b main
Breakpoint 1 at 0x804847d: file hook1.c, line 10.
(gdb) disas exit
Dump of assembler code for function exit@plt:
0x080483b0 <exit@plt+0>: jmp *0x804a00c
0x080483b6 <exit@plt+6>: push $0x18
0x080483bb <exit@plt+11>: jmp 0x8048370
End of assembler dump.

(gdb) r
Starting program: /home/zyd/projects/test
Missing separate debuginfo for /lib/ld-linux.so.2
Try: zypper install -C "debuginfo(build-id)=d7706cbaa0ca09319cb645eac789cb8399078797"
Missing separate debuginfo for /usr/lib/libstdc++.so.6
Try: zypper install -C "debuginfo(build-id)=7a23cb3bcf4e7a44325f46051276e8e7872f0ec7"
Missing separate debuginfo for /lib/libm.so.6
Try: zypper install -C "debuginfo(build-id)=a488c775caa0433cb23f9764d683150f40872ef1"
Missing separate debuginfo for /lib/libgcc_s.so.1
Try: zypper install -C "debuginfo(build-id)=d7d12f7fa08cc56782efdf6ec887bb02452b3646"
Missing separate debuginfo for /lib/libc.so.6
Try: zypper install -C "debuginfo(build-id)=ee302691046515fe3766ae3b7d47afd3e3a8d063"

Breakpoint 1, main (argc=1, argv=0xbffff104) at hook1.c:10
10 printf("\ntest generic function 11\n");
Current language: auto
The current source language is "auto; currently c++".
(gdb) disas exit
Dump of assembler code for function exit:
0xb7d59140 <exit+0>: push %ebx
0xb7d59141 <exit+1>: call 0xb7d418de <_Unwind_Find_FDE+114>
0xb7d59146 <exit+6>: add $0x12deae,%ebx
0xb7d5914c <exit+12>: sub $0x18,%esp
0xb7d5914f <exit+15>: movl $0x1,0x8(%esp)
0xb7d59157 <exit+23>: lea 0x330(%ebx),%eax
0xb7d5915d <exit+29>: mov %eax,0x4(%esp)
0xb7d59161 <exit+33>: mov 0x20(%esp),%eax
0xb7d59165 <exit+37>: mov %eax,(%esp)
0xb7d59168 <exit+40>: call 0xb7d59050
End of assembler dump.

(gdb)

在执行(gdb) r前后可以发现,exit对应的汇编代码内容发生了变化,这个是为什么呢 ?
头像
xhy
帖子: 3916
注册时间: 2005-12-28 1:16
系统: Ubuntu 12.10 X64
来自: 火星

Re: 为什么exit函数在run前后会发生变化?

#2

帖子 xhy » 2010-11-14 12:36

run之前,exit在libc里,只知道原型,只能反汇编出调用exit的代码,
run之后,exit已经在进程地址空间内,不仅知道原型,也知道实体。
目前负债150多万
vfdff
帖子: 21
注册时间: 2008-11-01 15:22

Re: 为什么exit函数在run前后会发生变化?

#3

帖子 vfdff » 2010-11-14 12:45

那为什么printf不是这么处理的 ?

(gdb) b main
Breakpoint 1 at 0x8048487: file hook1.c, line 15.
(gdb) b printf
Function "printf" not defined.
Make breakpoint pending on future shared library load? (y or [n]) y


Breakpoint 2 (printf) pending.
(gdb) b exit
Breakpoint 3 at 0x80483b0
(gdb)

为什么对同样的库函数,exit就定义了,而printf则没有定义呢 ?
头像
xhy
帖子: 3916
注册时间: 2005-12-28 1:16
系统: Ubuntu 12.10 X64
来自: 火星

Re: 为什么exit函数在run前后会发生变化?

#4

帖子 xhy » 2010-11-14 13:10

因为printf被优化成puts了
目前负债150多万
vfdff
帖子: 21
注册时间: 2008-11-01 15:22

Re: 为什么exit函数在run前后会发生变化?

#5

帖子 vfdff » 2010-11-14 16:18

( ⊙o⊙ )哇,果然使用puts代替printf时,这个puts函数也会这样的
#include <malloc.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <asm/page.h>
#include <sys/mman.h>

int as()
{
return 1;
}

int main(int argc,char** argv)
{
puts("\ntest generic function 11\n");

exit(1);
as();
puts("\ntest generic function 31\n");

return 0;
}

那我有什么办法得到这两个地址吗 ?
vfdff
帖子: 21
注册时间: 2008-11-01 15:22

Re: 为什么exit函数在run前后会发生变化?

#6

帖子 vfdff » 2010-11-14 16:20

不过我发现在使用cygwin时没有这个问题,难道cygwin没有libc库这种处理方法吗?而是直接进行连接
vfdff
帖子: 21
注册时间: 2008-11-01 15:22

Re: 为什么exit函数在run前后会发生变化?

#7

帖子 vfdff » 2010-11-14 16:22

xhy 写了:run之前,exit在libc里,只知道原型,只能反汇编出调用exit的代码,
run之后,exit已经在进程地址空间内,不仅知道原型,也知道实体。
run之前,exit在libc里,难道我使用了动态库吗?如果静态的话,程序应该连接进来了的
头像
linjiework
帖子: 240
注册时间: 2009-07-07 19:52

Re: 为什么exit函数在run前后会发生变化?

#8

帖子 linjiework » 2010-11-14 18:23

vfdff 写了:
xhy 写了:run之前,exit在libc里,只知道原型,只能反汇编出调用exit的代码,
run之后,exit已经在进程地址空间内,不仅知道原型,也知道实体。
run之前,exit在libc里,难道我使用了动态库吗?如果静态的话,程序应该连接进来了的

除非你在编译是用 -static 强制静态编译,否则系统默认是动态编译的。
阿呆 : 天下第一呆!
vfdff
帖子: 21
注册时间: 2008-11-01 15:22

Re: 为什么exit函数在run前后会发生变化?

#9

帖子 vfdff » 2010-11-14 21:44

zyd@HCC:~/projects> g++ hook1.c -o test -g -static
zyd@HCC:~/projects> gdb test
GNU gdb (GDB) SUSE (6.8.91.20090930-2.4)
Copyright (C) 2009 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "i586-suse-linux".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /home/zyd/projects/test...done.
(gdb) b main
Breakpoint 1 at 0x804826b: file hook1.c, line 15.
(gdb) disas exit
Dump of assembler code for function exit:
0x08048ab0 <exit+0>: sub $0x1c,%esp
0x08048ab3 <exit+3>: mov 0x20(%esp),%eax
0x08048ab7 <exit+7>: movl $0x1,0x8(%esp)
0x08048abf <exit+15>: movl $0x80cb00c,0x4(%esp)
0x08048ac7 <exit+23>: mov %eax,(%esp)
0x08048aca <exit+26>: call 0x80489d0 <__run_exit_handlers>

End of assembler dump.
(gdb) r
Starting program: /home/zyd/projects/test

Breakpoint 1, main (argc=1, argv=0xbffff104) at hook1.c:15
15 puts("\ntest generic function 11\n");
Current language: auto
The current source language is "auto; currently c++".
(gdb) disas exit
Dump of assembler code for function exit:
0x08048ab0 <exit+0>: sub $0x1c,%esp
0x08048ab3 <exit+3>: mov 0x20(%esp),%eax
0x08048ab7 <exit+7>: movl $0x1,0x8(%esp)
0x08048abf <exit+15>: movl $0x80cb00c,0x4(%esp)
0x08048ac7 <exit+23>: mov %eax,(%esp)
0x08048aca <exit+26>: call 0x80489d0 <__run_exit_handlers>

End of assembler dump.

哇,加了这个 -static 选项果然 run 前后一致了,不过和之前的两种情况下的代码好像又都不一致!
vfdff
帖子: 21
注册时间: 2008-11-01 15:22

Re: 为什么exit函数在run前后会发生变化?

#10

帖子 vfdff » 2010-11-14 21:50

linjiework 写了:
vfdff 写了:
xhy 写了:run之前,exit在libc里,只知道原型,只能反汇编出调用exit的代码,
run之后,exit已经在进程地址空间内,不仅知道原型,也知道实体。
run之前,exit在libc里,难道我使用了动态库吗?如果静态的话,程序应该连接进来了的

除非你在编译是用 -static 强制静态编译,否则系统默认是动态编译的。

代码: 全选

#include <malloc.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <asm/page.h>
#include <sys/mman.h>

#define MAX_FUNC_NUM       64           // 用于记录打桩函数同时使用的最大项
#define FLATJMPCODE_LENGTH 5            // 绝对跳转指令长度
#define TRUE        				0
#define FALSE       				1
typedef unsigned char 		BYTE;
typedef unsigned long 		ULONG;
typedef void          		*LPVOID;
typedef ULONG         		*FUNCPTR;         // 定义一个函数指针类型

// 用于记录函数指针地址的结构体
typedef class FuncList {
public:
    FUNCPTR newFuncList;
    FUNCPTR oldFuncList;
    BYTE oldFuncInstruct[FLATJMPCODE_LENGTH];
} FuncList_t;

static FuncList_t g_FuncList[MAX_FUNC_NUM] = { {NULL} }; // 用于记录函数指针地址

/*
   函数名称:ChangeProtection
   功能:    完成函数地址空间的读写权限的修改
   参数:
        [in]   ulAddr,待修改读写模式内存的开始地址
        [in]   ulLength,待修改读写模式内存的长度
        [in]   ulMode,  设置的读写权限值

   返回值:成功返回TRUE,失败返回FALSE
   说明: mprotect函数是专门设置虚拟内存区域属性的函数
*/
unsigned int ChangeProtection(const unsigned int ulAddr, const unsigned int ulLength, const unsigned int ulMode )
{
    unsigned int ulLastPageEnd;
    unsigned int ulFirstPageStart;

    /* Find the start address of the page to which the adress belongs to */
    ulFirstPageStart = ulAddr - (ulAddr % 4096);

    ulLastPageEnd  = ulAddr + ulLength;
    ulLastPageEnd += 4096 - (ulLastPageEnd % 4096);

    if (TRUE != mprotect((void*)ulFirstPageStart, ulLastPageEnd - ulFirstPageStart, ulMode ))
    {
        return FALSE;
    }

    return TRUE;
}

unsigned int ModProtectMemContent(unsigned long*  pBaseAddress, \
                                  const BYTE* pucInstruContent, unsigned long ulInstruLen)
{
    if (ChangeProtection((const unsigned int)pBaseAddress, ulInstruLen, PROT_WRITE | PROT_READ | PROT_EXEC) == FALSE)
    {
        return FALSE;
    }

    // 修改老函数入口处的指令,用上面准备的跳转指令修改
    (void)memcpy(pBaseAddress, pucInstruContent, ulInstruLen);

    if (ChangeProtection((const unsigned int)pBaseAddress, ulInstruLen, PROT_READ | PROT_EXEC) == FALSE)
    {
        return FALSE;
    }
    return TRUE;
}

/*
   函数名称:ReadProcessMemExt
   功能:    完成获取进程内容的功能,是对ReadProcessMemory的扩展
   参数:
        [in]   pBaseAddress,待写内存开始地址
        [in]   ulInstruLen,  要读取的内容的长度
        [out]   pucInstruContent,保存读到的内容
   返回值:成功返回TRUE,失败返回FALSE
*/
unsigned int ReadProcessMemExt(void*  pBaseAddress, \
                               unsigned long ulInstruLen, BYTE* pucInstruContent)
{
    if (ChangeProtection((const unsigned int)pBaseAddress, ulInstruLen, PROT_WRITE | PROT_READ | PROT_EXEC) == FALSE)
    {
        return FALSE;
    }

    // 获取指定内存的内容
    (void)memcpy(pucInstruContent, pBaseAddress, ulInstruLen);

    if (ChangeProtection((const unsigned int)pBaseAddress, ulInstruLen, PROT_READ | PROT_EXEC) == FALSE)
    {
        return FALSE;
    }

    return TRUE;
}

// 利用void*来传递参数,使通过g++编译
int ContainAdd(void *pOldFunc, void *pNewFunc)
{
    int i;
	int iInstruLen, iTmpLen;
    unsigned long dwNewInstructOffset;
	unsigned long dwOldJumpOffset;
	unsigned long dwTmpJumpOffset;
    BYTE aucSaveInstru[30];
    BYTE aBuf[10];
    
    //printf("Input(0) pOldFunc = 0x%x, pNewFunc = 0x%x!\n", pOldFunc, pNewFunc);
    //printf("Input(1) pOldFunc = 0x%x, pNewFunc = 0x%x!\n", &pOldFunc, &pNewFunc);
    //printf("Input(2) pOldFunc = 0x%x, pNewFunc = 0x%x!\n", ((unsigned long*)pOldFunc+1), ((unsigned long*)pNewFunc+1));
    //printf("Input(3) pOldFunc = 0x%x, pNewFunc = 0x%x!\n", ((unsigned long*)pOldFunc+2), ((unsigned long*)pNewFunc+2));
    // 查找未使用的空的结构体
    for (i=0; i<MAX_FUNC_NUM; i++) 
    {
        if(NULL == g_FuncList[i].oldFuncList) // 找到了未使用的空间
            break; 
    } // end for i
    if (MAX_FUNC_NUM <= i)
    {
        printf("The number of stub function if too many!\n");
        return FALSE;
    }

	  // 获取原函数开始的一段指令

    // 说明:由于函数地址在保护区域,不能用普通的修改内存值的方式实现,
    //       只能通过系统函数,来改变某个进程的内存内容方式实现
    ReadProcessMemExt((unsigned long*)pOldFunc, FLATJMPCODE_LENGTH, aucSaveInstru);
    // 先得到原函数调用地址的跳转偏移,为下面计算相对偏移做准备
    // 将新函数指针和原函数调用地址保存到列表中
    g_FuncList[i].oldFuncList = (FUNCPTR)pOldFunc;
    g_FuncList[i].newFuncList = (FUNCPTR)pNewFunc;
    memcpy(g_FuncList[i].oldFuncInstruct, aucSaveInstru, FLATJMPCODE_LENGTH);

    // 得到新函数指令跳转值
    dwNewInstructOffset = ((ULONG)pNewFunc - (ULONG)pOldFunc - FLATJMPCODE_LENGTH); 
    // 修改老函数入口处的指令,用跳转指令替代
    // 准备要填入的指令机器码,需要修改5个字节内容
    aBuf[0] = 0xE9; // 0XE9为相对跳转指令的机器码,具体可以参考intel手册
    *(ULONG*)((BYTE*)aBuf + 1) = dwNewInstructOffset;
    if ( FALSE == ModProtectMemContent((ULONG*)pOldFunc, aBuf, FLATJMPCODE_LENGTH) )
    {
        printf("\r\nModProtectMemContent fail!\n");
        return FALSE;
    }

    return TRUE;
}

//////////////////////////////////////////////////////////
void exit_stub(int status)
{
	  char a[]="hello world";
	  printf("exit_stub %d %p, %p\n", status, &status, exit_stub);
	  printf("exit %d %p, %p\n", status, &status, exit);
	  //exit(1);
	  return ;	
}
//////////////////////////////////////////////////////////////////////

int main(int argc,char** argv)
{
    printf("\ntest generic function 11\n");
	  ContainAdd((void*)(exit), (void*)(exit_stub));
    
    printf("Input(0) pOldFunc = 0x%x, pNewFunc = 0x%x!\n", exit, exit_stub);
    exit_stub(1);
    exit(1);

    printf("\ntest generic function 31\n");

    return 0;
}
那你知道我上面的程序运行的时候为什么会挂掉吗 ?
回复