打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
EXE导出函数
EXE导出函数
突然想到个问题,EXE可否像DLL一样导出函数呢?于是就起来做试验——
静态链接调用
嗯,先建立一个EXE,内容很简单:
#include "stdafx.h"
#define EXE_LIBRARY
#include "ExeLibrary.h"
EXE_LIBRARY_API int Sum(int a, int b)
{
return a + b;
}
int APIENTRY _tWinMain(_In_ HINSTANCE     hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPTSTR        lpCmdLine,
_In_ int           nCmdShow)
{
return 0;
}
Sum函数是等下用来测试的。其中ExeLibrary.h 中模仿系统生成的DLL头文件进行了宏定义:
#ifdef EXE_LIBRARY
#define EXE_LIBRARY_API __declspec(dllexport)
#else
#define EXE_LIBRARY_API __declspec(dllimport)
#endif
EXE_LIBRARY_API int Sum(int a, int b);
然后建立另一个EXE:
#include "../ExeLibrary/ExeLibrary.h"
#pragma comment(lib, "ExeLibrary.lib")
int main()
{
int s = Sum(1, 2);
return s;
}
然后运行:
能跑,运行结果也正确,说明函数被正确执行。
查了下进程列表,木有ExeLibrary.exe。
再查调试环境的模块列表:
ExeLibrary赫然在目。
整个运行过程中,WinMain 函数木有进入。尝试
动态加载调用
先为刚才的ExeLibrary添加一个def文件:
LIBRARY
EXPORTS
Sum
然后新建另一个EXE:
#include <Windows.h>
#include <tchar.h>
int main()
{
HMODULE hModule = LoadLibrary(_T("ExeLibrary.exe"));
if (hModule == nullptr)
{
return 0;
}
typedef int (*FnSum)(int a, int b);
FnSum Sum = (FnSum)GetProcAddress(hModule, "Sum");
int s = 0;
if (Sum != nullptr)
{
s = Sum(1, 2);
}
FreeLibrary(hModule);
return s;
}
运行结果:
同刚才一样,一切正常。
结论
可以像DLL一样,在EXE里导出函数。调用时可以静态链接也可以动态加载。EXE只作为一个进程内的模块被加载,不会新起一个进程。加载过程中EXE中的WinMain函数不会被调用。
例程下载:http://sdrv.ms/11dzfIT
posted on 2012-12-01 11:41溪流 阅读(1437)  编辑 收藏引用 所属分类:Windows
评论:# re: EXE导出函数 2012-12-01 13:15 |OwnWaterloo
exe和dll还是有很多区别的。
首先entrypoint肯定就被忽略了。
其次重定位和依赖加载好像也会有问题。
比如试试这个?
int error_code;
__declspec(dllexport) int get_error(void) { return error_code; }
__declspec(dllimport) void set_error(int x) { error_code = x; }
get和set产生的指令里会有error_code的地址。
如果是dll,加载时指令中的地址会被正确地重定位。
而exe不行,即使保留重定位信息也不行。
exe可以被加载应该是为了里面的资源而不是代码。
回复  更多评论
# re: EXE导出函数 2012-12-02 12:09 |溪流
@OwnWaterloo
不懂,为啥例子中一个是export一个是import?  回复  更多评论
# re: EXE导出函数 2012-12-02 16:36 |OwnWaterloo
@溪流
手误。。。我的错。。。  回复  更多评论
# re: EXE导出函数 2012-12-02 16:48 |OwnWaterloo
@溪流
两个都是dllexport。两个函数产生的代码都会用上error_code的地址。
链接产生这个dll/exe的时候,是很难确切地知道加载后该dll/exe的地址。同样也很难确切知道error_code的地址,因为它和dll/exe被加载后的基地址之间的偏移是链接后就固定了的。
于是链接器只能先假设dll/exe会被加载到某个位置(首选基地址),然后根据它产生代码。
比如一种极端情况,将dll/exe复制一份本地文件(首选基地址相同),然后loadlibrary它俩。
那么,至少有一个dll/exe是无法被加载到首选基地址的,也就是set/get的指令中使用的地址是不正确的。
如果是dll,没有被加载到首选基地址的话,就会发生重定项。set/get的指令会相应的修改。
而exe,我记得loader就不会做这个工作,于是就。。。
回复  更多评论
# re: EXE导出函数 2012-12-02 16:50 |OwnWaterloo
@溪流
另外,load是不会根据dll/exe后缀名来判断是否是dll还是exe。
它是根据pe格式中的一个域来判断的。具体位置我忘了。。。不过dumpbin好像能显示出来。
也就是说。。。很多那些后缀是exe(甚至是ocx什么的),而且加载后也能成功调用里面函数的文件,其实按pe格式来说都是dll文件,只是后缀名没有用dll而已。
回复  更多评论
# re: EXE导出函数 2012-12-02 16:58 |溪流
@OwnWaterloo
懂了~就是说EXE当DLL用,不会被重定向咯?改天试下哈  回复  更多评论
# re: EXE导出函数 2012-12-02 23:47 |朱峰everettjf
当时我也做了个类似的研究,
http://www.cppblog.com/everett/archive/2012/05/25/176073.aspx
交流交流  回复  更多评论
# re: EXE导出函数 2012-12-04 10:58 |zuhd
@OwnWaterloo
我做了个实验,exe的导出函数中调用全局变量也是没问题的,没出现楼上所说的崩溃现象,如果按照pe格式来理解的话,即使是exe应该也会被重定向吧?  回复  更多评论
# re: EXE导出函数 2012-12-04 11:00 |zuhd
楼主的例子我没下载成功,用的是朱峰everettjf提供看雪里的例子,我新增加了两个函数,调用全局变量。如果需要提供现场的话,留个邮箱。  回复  更多评论
# re: EXE导出函数 2012-12-04 15:12 |OwnWaterloo
@zuhd
没有崩溃=没有问题=程序正确?
*(int*)随便写个什么地址 = 12; // 只要运气好,同样不会立即崩溃。  回复  更多评论
# re: EXE导出函数 2012-12-04 16:13 |OwnWaterloo
本来应该用高级评论将重点高亮的,但不知道cppblog出了什么问题用不了。只能眼睛尖点了。。。
1. test files
module.c 导出1个变量与3个函数
__declspec(dllexport) int error_code;
__declspec(dllexport) int get(void) { return error_code; }
__declspec(dllexport) void set(int x) { error_code = x; }
int main(void) { return 0; }
main.c 输出变量地址、函数地址以及函数包含的指令
#include <stdio.h>
__declspec(dllimport) int error_code;
__declspec(dllimport) int get(void);
__declspec(dllimport) void set(int x);
int main(void)
{
int i;
unsigned char const* p;
printf("%p\n", (void*)&error_code);
p = (unsigned char*)get;
printf("%p:", p);
for (i=0; i<12; ++i) printf(" %02X", p[i]);
printf("\n");
p = (unsigned char*)set;
printf("%p:", p);
for (i=0; i<12; ++i) printf(" %02X", p[i]);
printf("\n");
return 0;
}
2. dll
编译
cl /LD /O1 module.c /link /noentry
查看首选基地址
dumpbin /all module.dll | find /i "image base"
10000000 image base (10000000 to 10004FFF)
查看反汇编与重定项
dumpbin /disasm /relocations module.dll
File Type: DLL
10001000: A1 00 30 00 10 mov eax,dword ptr ds:[10003000h]
10001005: C3 ret
10001006: 8B 44 24 04 mov eax,dword ptr [esp+4]
1000100A: A3 00 30 00 10 mov dword ptr ds:[10003000h],eax
1000100F: C3 ret
BASE RELOCATIONS #4
1000 RVA, C SizeOfBlock
1 HIGHLOW 10003000
B HIGHLOW 10003000
10001000 处(get的第1条)指令的操作数(地址在10001001)是 10003000
1000100A 处(set的第2条)指令的操作数(地址在1000100B)也是 10003000
注意"File Type: DLL",这是根据PE的域来的。
编译并运行得到的输出是
cl main.c module.lib && main.exe
10003000
10001000: A1 00 30 00 10 C3 8B 44 24 04 A3 00
10001006: 8B 44 24 04 A3 00 30 00 10 C3 00 00
error_code的地址和指令中使用的地址是相同的。
3. dll relocation
上面 module.dll 恰好加载在首选基地址,所以没有发生重定项。
要演示重定项发生的情况, 可以将 module.dll 复制一份, 然后用 LoadLibrary 加载。
或者直接首选基地址为一个会冲突的, exe的默认基地址0x400000。
cl /LD /O1 module.c /link /noentry /base:0x400000
dumpbin /all module.dll | find /i "image base"
400000 image base (00400000 to 00404FFF)
dumpbin /disasm /relocations module.dll
00401000: A1 00 30 40 00 mov eax,dword ptr ds:[00403000h]
00401005: C3 ret
00401006: 8B 44 24 04 mov eax,dword ptr [esp+4]
0040100A: A3 00 30 40 00 mov dword ptr ds:[00403000h],eax
0040100F: C3 ret
BASE RELOCATIONS #4
1000 RVA, C SizeOfBlock
1 HIGHLOW 00403000
B HIGHLOW 00403000
cl main.c module.lib && main.exe
00393000
00391000: A1 00 30 39 00 C3 8B 44 24 04 A3 00
00391006: 8B 44 24 04 A3 00 30 39 00 C3 00 00
对比 dumpbin 得到的反汇编与 main.exe 的输出,可以发现指令中的操作数有相应的修改,以正确的使用00393000上的变量error_code。
4. dll fixed
如果链接时选择基地址固定
cl /LD /O1 module.c /link /noentry /base:0x400000 /fixed
产生的dll里就没有重定项信息
dumpbin /relocations module.dll
并且选择的是一个肯定会冲突的基地址,所以加载main.exe就会失败。
main.exe
5. exe export
默认exe是不会包含重定项信息的
cl /O1 module.c && dumpbin /relocations module.exe
File Type: EXECUTABLE IMAGE
注意"File Type: EXECUTABLE IMAGE",这是根据PE的域来的。
并且首选基地址也是冲突的。
dumpbin /all module.exe | find /i "image base"
400000 image base (00400000 to 0040BFFF)
但是让 main.c 链接到 module.exe 可以运行成功(之前dll fixed的情况是加载 main.exe 失败)
cl main.c module.lib & main.exe
0039B700
00391000: A1 00 B7 40 00 C3 8B 44 24 04 A3 00
00391006: 8B 44 24 04 A3 00 B7 40 00 C3 33 C0
注意指令里的操作码,并没有修改为error_code的地址:0039B700。
如果真的调用了get和set,也只是读写了其他的地址,而不是error_code。
bug已经产生了。 没崩只是运气, 那个地址恰好有读写权限。
而且实验代码一般都比较短,跑完马上就退出了,这种意外的写入产生的影响也不一定能发现。
6. exe export with relocation information
可以用 /fixed:no 附带上重定项信息
cl /O1 module.c /link /fixed:no
dumpbin /relocations module.exe 会产生很多输出,因为它还引用了libc。
而让 main.c 链接到 module.exe 并运行的同样不会发生重定项
cl main.c module.lib & main.exe
0039B700
00391000: A1 00 B7 40 00 C3 8B 44 24 04 A3 00
00391006: 8B 44 24 04 A3 00 B7 40 00 C3 33 C0
回复  更多评论
# re: EXE导出函数 2012-12-05 00:11 |溪流
@OwnWaterloo
呀,信息量好大,学习了~!本想到周末研究一番的~  回复  更多评论
# re: EXE导出函数 2012-12-05 09:44 |zuhd
@OwnWaterloo
可以这样理解吗?
1,用lib的方式加载exe的导出函数,
#pragma comment(linker,"/FIXED:NO") ,
这样exe就和dll一样,均不会有问题。
2,用loadlibrary的方式加载exe的到处函数,
即使是重定向,在操作全局变量,也不会定向到全局变量的正确地址。
如:
而让 main.c 链接到 module.exe 并运行的同样不会发生重定项
cl main.c module.lib & main.exe
0039B700
00391000: A1 00 B7 40 00 C3 8B 44 24 04 A3 00
00391006: 8B 44 24 04 A3 00 B7 40 00 C3 33 C0  回复  更多评论
# re: EXE导出函数 2012-12-05 14:28 |OwnWaterloo
@zuhd
我记得是只与文件类型(PE中的那个域)有关,与加载方式(隐式加载/显式加载)无关。
只要文件类型是exe,加载器就不会去处理重定项。
如果exe没有加载到首选基地址,里面的指令操作的就不是预想中的地址。
前面说loadlibrary的意思是:这是一种让dll/exe无法加载到首选基地址的方法。
如果将dll/exe文件复制一份(文件各种信息都是相同的),然后用loadlibrary加载这两者,那两者之一肯定之多有一个是被加载到首选基地址。于是就可以观察另一个的情况了。
但前面为了偷懒。。。 就没有用这个方法,而是用/base:0x400000 —— 这个是exe文件默认的基地址 —— 让dll无法加载到首选基地址。
回复  更多评论
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
dll创建和使用
C 编程笔记:dll的生成与使用
动态加载DLL的方法与注意的问题
C#调用C++类库dll,无法找到函数入口(无法在“***.dll“中找到名为“***“的入口点)
DLL编写教程
C语言第一个DLL程序
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服