覆盖函数调用的最直接方法是使用 VxWorks 的加载时间链接。考虑以下来源:
文件1.c:
#include <stdio.h>
int function1 (void);
int function1 ()
{
printf ("function1 called\n");
return 1;
}
文件2.c:
#include <stdio.h>
int function2 (void);
int function2 ()
{
printf ("function2 called\n");
return 2;
}
文件 3.c:
int function1 (void);
int function2 (void);
int function3 (void);
int function3 ()
{
function1 ();
function2 ();
return 0;
}
模拟.c:
#include <stdio.h>
int function1 (void);
int function2 (void);
int function1 ()
{
printf ("mock function1 called\n");
return 1;
}
int function2 ()
{
printf ("mock function2 called\n");
return 2;
}
当您加载一个对象时,它的函数被添加到全局符号表中。
-> ld < file1.o
value = 273740816 = 0x1050f410
-> lkup "function"
function1 0x108b0000 text (file1.o)
value = 0 = 0x0
->
当您加载使用符号表中已有函数的对象时,每次调用都将立即解析为与表中该符号关联的最后一个地址。
-> ld < file2.o
value = 292535232 = 0x116fbbc0
-> ld < file3.o
value = 292537592 = 0x116fc4f8
-> lkup "function"
function1 0x108b0000 text (file1.o)
function2 0x108d0000 text (file2.o)
function3 0x108f0000 text (file3.o)
value = 0 = 0x0
-> l function3
function3:
0x108f0000 55 PUSH EBP
0x108f0001 89 e5 MOV EBP, ESP
0x108f0003 56 PUSH ESI
0x108f0004 57 PUSH EDI
0x108f0005 e8 f6 ff fb ff CALL function1
0x108f000a e8 f1 ff fd ff CALL function2
0x108f000f 31 c0 XOR EAX, EAX
0x108f0011 5f POP EDI
0x108f0012 5e POP ESI
0x108f0013 89 ec MOV ESP, EBP
value = 0 = 0x0
-> function3
function1 called
function2 called
value = 0 = 0x0
->
尽管l()
显示函数名称很有帮助,但实际上并没有将符号与对象一起加载到内存中。相反,会加载对与函数关联的最后一个地址的调用。因此,可以通过加载另一个同名函数来覆盖先前加载的函数。
-> unld "file3.o"
value = 0 = 0x0
-> ld < mock.o
value = 292537592 = 0x116fc4f8
-> ld < file3.o
value = 292539496 = 0x116fcc68
-> lkup "function"
function1 0x108f0000 text (mock.o)
function1 0x108b0000 text (file1.o)
function2 0x108f0020 text (mock.o)
function2 0x108d0000 text (file2.o)
function3 0x10910000 text (file3.o)
value = 0 = 0x0
-> l function3
function3:
0x10910000 55 PUSH EBP
0x10910001 89 e5 MOV EBP, ESP
0x10910003 56 PUSH ESI
0x10910004 57 PUSH EDI
0x10910005 e8 f6 ff fd ff CALL function1
0x1091000a e8 11 00 fe ff CALL function2
0x1091000f 31 c0 XOR EAX, EAX
0x10910011 5f POP EDI
0x10910012 5e POP ESI
0x10910013 89 ec MOV ESP, EBP
value = 0 = 0x0
-> function3
mock function1 called
mock function2 called
value = 0 = 0x0
->
请注意,要使此方法起作用,被调用函数和调用函数不能编译到同一个对象中。您可能还注意到要调用的地址与符号表中的地址不匹配。这是在 VxSim 中执行上述操作的结果。VxSim 加载器实际上调用了底层操作系统的加载器。因此,这些地址与符号表中的地址不匹配,并且程序集反映了运行 WorkBench 的底层 Pentium 架构。
也可以通过直接操作内存中要调用的地址来覆盖函数调用。此方法将取决于实现。下面演示了使用 gcc -mlongcall 选项为 PPC 编译的源代码。这是在实际目标上运行的,而不是 VxSim。
-> ld < file1.o
value = 33538216 = 0x1ffc0a8 = function1 + 0x498
-> ld < file2.o
value = 33548336 = 0x1ffe830 = function2 + 0x80
-> ld < mock.o
value = 33549600 = 0x1ffed20 = function2 + 0x570
-> ld < file3.o
value = 33550744 = 0x1fff198 = function2 + 0x9e8
->
-> lkup "function"
function1 0x01ffbef8 text (mock.o)
function1 0x01ffbc10 text (file1.o)
function2 0x01ffbf58 text (mock.o)
function2 0x01ffe7b0 text (file2.o)
function3 0x01ffe558 text (file3.o)
value = 0 = 0x0
->
-> function3
mock function1 called
mock function2 called
value = 0 = 0x0
->
-> l function3
function3:
0x1ffe558 9421ffe8 stwu r1,-24(r1)
0x1ffe55c 7c0802a6 mfspr r0,LR
0x1ffe560 93a1000c stw r29,12(r1)
0x1ffe564 93c10010 stw r30,16(r1)
0x1ffe568 93e10014 stw r31,20(r1)
0x1ffe56c 9001001c stw r0,28(r1)
0x1ffe570 7c3f0b78 or r31,r1,r1
0x1ffe574 3d200200 lis r9,512
0x1ffe578 3ba9bef8 addi r29,r9,-16648
0x1ffe57c 7fa803a6 mtspr LR,r29
value = 33547648 = 0x1ffe580 = function3 + 0x28
->
-> *0x1ffe578
function3 + 0x20 = 0x1ffe578: value = 1000980216 = 0x3ba9bef8
-> *0x1ffe578 = 0x3ba9bc10
function3 + 0x20 = 0x1ffe578: value = 1000979472 = 0x3ba9bc10
-> *0x1ffe578
function3 + 0x20 = 0x1ffe578: value = 1000979472 = 0x3ba9bc10
->
-> function3
function1 called
mock function2 called
value = 0 = 0x0
->
显然,直接操作内存中的指针很快就会变得乏味。此外,内存保护将阻止您更改 RTP 或 VxSim 中加载的对象。(因此,为什么我在实际硬件上运行它。)我提到了这种可能性,主要是因为它似乎最符合您的问题陈述。
最后,对于重要的单元测试,您可能需要考虑专门为该任务设计的工具。尝试搜索“vxworks 单元测试框架”。我对任何特定工具都没有丰富的经验(也不想遇到垃圾邮件)。也许,这里的其他人可以提供一个很好的建议。