`
driftcloudy
  • 浏览: 132182 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

从Entry Point到main函数调用(4)

    博客分类:
  • C
阅读更多

先撇开_ioinit()不谈,IO有点儿麻烦,待有空再去挖掘这些东西。

GetCommandLineA

该函数的汇编代码短得令人发指。

7C812FBD    mov     eax, dword ptr [7C8855F4]
7C812FC2    retn 

可见7C8855F4是个特殊的内存地址,其中存放了一个指向命令行缓冲区的指针。通过该指针,可以访问整个exe的完整路径以及后面附加的参数。

 

另外,通过PEB 中的ProcessParameters 也能够访问到CommandLine,但是通过这种方式访问到得是一个Unicode 字符串,而GetCommandLineA 访问的则是ANSI字符串。

 

__crtGetEnvironmentStringsA

在A_ENV.c 文件中可以看到该函数的具体实现。在文件开头有如下描述:

写道
Internal support function. Since GetEnvironmentStrings returns in OEM and we want ANSI ( note that GetEnvironmentVariable returns ANSI! ) and SetFileApistoAnsi( ) does not affect it, we have no choice but to obtain the block in wide character and convert to ANSI.

最后返回的是一个指向当前进程的环境变量的指针,这里所谓的环境变量用一种类似于键值对的方式存储在内存中。

注意首先得到是wide character 版本的环境变量,随后需要将它们转化成ANSI 。

 

来看一下__crtGetEnvironmentStringsA 的实现:

LPVOID __crtGetEnvironmentStringsA( VOID  )
{
        static int f_use = 0;
        wchar_t *wEnv = NULL;
        wchar_t *wTmp;
        char *aEnv = NULL;
        char *aTmp;
        int nSizeW;
        int nSizeA;

        /*
         * Look for 'preferred' flavor. Otherwise use available flavor.
         * Must actually call the function to ensure it's not a stub.
         */
        if ( 0 == f_use )
        {
            if ( NULL != (wEnv = GetEnvironmentStringsW()) )
                f_use = USE_W;

            else if ( NULL != (aEnv = GetEnvironmentStringsA()) )
                f_use = USE_A;

            else
                return NULL;
        }
        
        /* Use "W" version */
        if (USE_W == f_use)
        {        
                 ……        
        }

        /* Use "A" version */
        if ( USE_A == f_use )
        {
                 ……        
        }
        
        return NULL;
}
 

可见实质上,__crtGetEnvironmentStringsA 函数依然是调用了win API,可能是:

GetEnvironmentStringsW  或 GetEnvironmentStringsA

 

有人也许奇怪,为什么__crtGetEnvironmentStringsA 不直接去调用GetEnvironmentStringsA 呢?原因在于,即使是用GetEnvironmentStringsA ,所获取的也并非是 ANSI 的环境变量。

GetEnvironmentStringsA uses the OEM codepage .The "ANSI" version of GetEnvironmentStrings (GetEnvironmentStringsA) returns the strings in the OEM code page, not the ANSI code page . If you need the returned strings in the ANSI code page call the Unicode version (GetEnvironmentStringsW) and translate the returned wide-string to an 8-bit string using a conversion function such as WideCharToMultiByte. Or call GetEnvironmentVariableA, which correctly uses the ANSI code page.

这里很清楚地写到,GetEnvironmentStringsA 返回的其实是OEM 字符,并非 ANSI。如果最终需要获得 ANSI 的环境变量,可以使用 GetEnvironmentStringsW ,然后将所获得的结果从 wide-string 转化成 8-bit string 。

 

因此,__crtGetEnvironmentStringsA 中首先调用了 GetEnvironmentStringsW 之后,设置 f_use = USE_W , 再进入if (USE_W == f_use) 的 语句块,进入转化成ANSI的处理。

 

/* 
     这里将指针wTmp 移动到 environment block 的末尾处 
     特别要注意,wTmp 是一个 wchar_t 类型的指针,因此 ++ 是移动两个字节
     例如 environment block 的末尾是:
         45 00   64 00   69 00   74 00   00 00   00(移动到此处)
         E       d       i       t       \0      00
*/
wTmp = wEnv;
while ( *wTmp != L'\0' ) {
	if ( *++wTmp == L'\0' )
		wTmp++;
}

/* 求出environment block 中wide char的个数 +1 */
nSizeW = wTmp - wEnv + 1;

/* 该API返回转化为byte字符的字节数,其实nSizeA 和 nSizeW  数值相同 */
nSizeA = WideCharToMultiByte( CP_ACP,0,wEnv,nSizeW,NULL,0,NULL,NULL );

/* 分配一块大小为nSizeA 的内存 */
if ( (nSizeA == 0) || ((aEnv = (char *)_malloc_crt(nSizeA)) == NULL) )
{
	FreeEnvironmentStringsW( wEnv );
	return NULL;
}

/* 这里将WideChar 转化为 byte ,并且存放到刚分配的内存区域中 */
if ( !WideCharToMultiByte( CP_ACP,0,wEnv,nSizeW,aEnv,nSizeA,NULL,NULL ) )
{
	_free_crt( aEnv );
	aEnv = NULL;
}

FreeEnvironmentStringsW( wEnv );
return aEnv;

这里的实现与我想象中有些差别,不太清楚为什么MS的coder 会两次调用WideCharToMultiByte 函数对environment block 进行转化。

 

_setargv

该函数负责为C 程序的"argc" 和 "argv" 参数作好准备,_setargv 的源代码位于 STDARGV.c 文件。它会读取 _acmdln(存放了之前GetCommandLineA返回的指针)来访问command line,然后最关键的步骤是通过调用parse_cmdline 函数来解析command line,并且创建argv 数组。

 

这里仅分析_setargv 中最为核心的代码行。

_TSCHAR *p;
_TSCHAR *cmdstart;                  /* start of command line to parse */
int numargs, numchars;

//MAX_PATH 是 260 ,很有趣 ,文件的完整路径最大255 + "." + 后缀(比如exe) + " \0"
static _TSCHAR _pgmname[ MAX_PATH ];


/* 
 * __initmbctable 只能被调用一次,因此会设置一个__mbctype_initialized标记。
 * __initmbctable 内部会调用_setmbcp 函数去创建一个新的multibyte code page,
 * 随后置__mbctype_initialized=1
 */
if ( __mbctype_initialized == 0 )
        __initmbctable();


/* 将当前进程的exe完整路径复制到_pgmname数组中
 * 注意GetModuleFileName 是拿不到 程序启动参数args的,它获得仅仅是程序的完整路径而已
 */
GetModuleFileName( NULL, _pgmname, sizeof( _pgmname ) / sizeof(_TSCHAR));
_pgmptr = _pgmname;

/* 如果之前解析出来的_acmdln为空,则采用_pgmptr */
cmdstart = (*_acmdln == NULCHAR) ? _pgmptr : _acmdln;

/* 计算出 numargs 和 numchars 的大小  */
parse_cmdline(cmdstart, NULL, NULL, &numargs, &numchars);

/* 
 * 为argv 分配所需的空间 
 * 先是numargs 个指针,前numargs-1 指向路径与参数,最后一个是NULL
 * 紧接着是numchars 个字符,用来存放numargs-1 个指针所指的内容
 */
p = _malloc_crt(numargs * sizeof(_TSCHAR *) + numchars * sizeof(_TSCHAR));
if (p == NULL)
        _amsg_exit(_RT_SPACEARG);

/* 为指针P所指向的内存空间里填充argv  */
parse_cmdline(cmdstart, (char **)p, p + numargs * sizeof(char *), &numargs, &numchars);

/* 至此,argc 与  argv 已经全部现形 */
__argc = numargs - 1;
__argv = (char **)p; 

 

numargs 和 numchars 可能比较难以理解。举例来说,现有test0.exe,运行时附加了三个参数 a b c

 

cmdstart 指向的内存地址是0x00141ee0:

这里一共占用了 33 个byte (包含末尾的 00)

 

最后指针 P 指向的内存地址是0x003812c0:

很显然,0x003812c0 开始是指针数组,其中包含了4个有效指针,1个空指针。接着指针数组后的是4个ANSI字符串(红色线框标出),每个字符串以"00"结尾。

所以,0x003812c0 的大小 = 5个指针 + 4个字符串。

numargs   = 5,表示指针数组的大小 ; numchars = 31(0x1F),表示四个字符串一共占用的字节数 。

 

__argc = 5-1

__argv = 0x003812c0

注意,最后返回的时候,需要将numargs-1,毕竟最后1个是空指针。

 

 

 

 

 

 

0
1
分享到:
评论

相关推荐

    windows设置Entry Point的方法

    请注意,改变Entry Point可能会影响到C++运行时库的初始化和清理过程,因此在进行此类更改时需要确保你的代码能够正确处理这些变化。如果自定义的入口点没有调用`mainCRTStartup`或`WinMainCRTStartup`,可能会导致...

    (12.2)--可执行文件的加载1

    3. 加载完成和执行main函数:加载完成后,将PC(EIP)设定指向Entry point(即符号_start处),最终执行main函数,以启动程序执行。 ELF头信息 ELF头信息是可执行文件的元数据,用于描述可执行文件的结构和组织...

    fn-args-main-源码.rar

    2. **主入口点(Main Entry Point)** `main`通常指的是程序的主要执行入口。在`fn-args-main`中,`main`可能是库的核心功能,负责接收用户输入,处理参数,然后启动相应的功能。这个主入口点可能会调用一系列内部...

    通过SMBios读UUID源码(C语言)

    这将允许你从命令行或其他应用程序接口调用`read_smbios_uuid`函数,从而获取系统的UUID信息。 总的来说,理解SMBIOS和UUID在系统管理中的作用,以及如何使用C语言来处理它们,是系统级编程和硬件诊断的重要技能。...

    python 波形生成-26-管理系统框架之程序入口函数.ev4.rar

    通常,程序入口函数(entry point)是整个系统开始运行的地方,它负责调用不同模块,初始化系统,以及处理命令行参数。在Python中,`if __name__ == '__main__':` 是定义程序入口的常见方式,这样可以确保只有在直接...

    OSE操作系统学习总结借鉴.pdf

    OSE 的全局变量与初始化OSE 的 Main 函数就调用一个函数start_OSE。在 start_OSE 函数中首先调用 odo_config_start_handler1 对系统的硬件进行初始化。再调用 odo_init_os 进行 OSE 操作系统初始化。odo_init_os 这...

    HOOK-IAT.rar_IAT_iat hook_pe iat_pe 入口点hook_入口点

    IAT Hook是一种代码注入技术,用于修改目标程序的IAT,将函数调用重定向到自定义的处理函数,而不是原始的函数实现。这通常用于调试、监控、安全检测或恶意软件行为。例如,一个常见的应用是防止病毒通过替换系统API...

    VC下Dll的创建以及使用

    显式调用则是通过运行时动态加载 DLL,并手动调用其中的函数。 - **加载 DLL**:使用 `LoadLibrary` 函数加载 DLL 文件。 - **获取函数地址**:使用 `GetProcAddress` 函数获取导出函数的地址。 - **释放 DLL**:...

    masm.rar_汇编 dos

    4. **堆栈段(Stack Segment)**:用于存储函数调用时的参数、返回地址等临时数据。 5. **程序入口点(Entry Point)**:程序开始执行的地方。 6. **DOS中断调用(DOS Integers)**:DOS提供了一系列中断调用来执行...

    Windows以后台服务的形式启动程序

    3. **服务初始化函数(Service Entry Point)**:在创建服务时,操作系统会调用这个函数,通常是`WinMain()`或`DllMain()`。 4. **服务注册(Registering the Service)**:通过`CreateService()`函数将服务添加到...

    VC6.0链接错误的一些解决办法

    此时,应进入“Link”属性页,选择“Output”,然后在“Entry-point symbol”中输入`wWinMainCRTStartup`。 4. **线程运行时库设置错误**:如果你的代码涉及线程创建,如`__beginthreadex`或`__endthreadex`,而...

    《你必须知道的495个C语言问题》

    可我找不到任何方法来声明这样的函数——感觉我需要一个返回指针的函数,返回的指针指向的又是返回指针的函数……,如此往复,以至无穷。 12  数组大小 13 1.23 能否声明和传入数组大小一致的局部数组,或者由...

    11A_VenusFW_Customization.pdf

    3. **启动函数调用**:在`vtst_rt_my_first_app_launch`函数中调用实际的启动函数,以显示应用。 #### 三、定义应用类(Step3: Define the App Class: VappMyFirstApp) 定义应用的具体类,以便更好地管理和控制...

    嵌入式+QT实验.docx

    - 需要在.pro文件中添加`QT += widgets`,以便能够在main函数中引入`QApplication`。 - **实验二:纯代码方式编写Hello World**:通过纯代码的方式构建QT程序,了解不同构建方法的区别。 - **实验三:多窗口程序**...

    C语言FAQ 常见问题列表

    o 5.8 我看到了用指针调用函数的不同语法形式。到底怎么回事? o 5.9 我怎样把一个 int 变量转换为 char * 型?我试了类型转换, 但是不行。 * 6. 空 (null) 指针 o 6.1 臭名昭著的空指针到底是什么? o 6.2 ...

    金盾提取脚本

    对于未经过封装的程序而言,OEP通常是程序的主函数(main函数)的地址。在Molebox封装的情况下,OEP被修改,因此需要通过特定的技术手段来恢复它。 ### 3. 脚本解析 #### 脚本流程概述 脚本首先检查当前环境是否...

    PE文件节区添加并弹出简单的对话框

    同时,可能还需要调整入口点(Entry Point)以便程序运行时调用对话框代码。这通常涉及到对PE文件的二进制级操作,需要谨慎处理,避免破坏原始文件的完整性。 在提供的压缩包文件6d17ec4037f449ee902613fec0d6cc12...

    win32课程文档

    3. **入口函数 (Entry Point):** - 控制台程序: `main` 函数作为入口。 - 图形用户界面程序: `WinMain` 函数作为入口。 - 动态链接库: `DllMain` 函数作为入口。 4. **执行模式:** - 控制台程序: 依赖于 DOS ...

    arm启动代码

    - **启动代码**:指的是处理器在复位后执行的第一条指令到进入C语言`main()`函数之前的那部分汇编代码。这部分代码负责完成基本硬件配置、内存初始化等工作。 - **Bootloader**:是一个具有引导装载功能的完整程序...

Global site tag (gtag.js) - Google Analytics