我的问题如下。我有一个由各种c文件组成的代码单元,例如
这些都是用GCC编译成一个唯一的对象“object.o”,然后它与其他对象链接,最后给出在VxWorks上运行的可执行文件“application.out”。
由于我正在对“object.o”进行单元测试,我需要通过代码激发所有可能的方法。具体来说,有些情况下我应该执行模拟函数而不是原始函数,以模拟错误发生。 假设有一个函数“func_caller”,我试图测试它,在执行的某个时刻,调用另一个函数“func_called”(声明为静态)。
由于我不想修改原始代码,我想知道是否有办法以这样的方式操作指令指针:当调用“func_called”时,它实际上执行另一个模拟函数“func_called_mock”和来电者“func_caller”没有注意到任何事情。
提前致谢。
答案 0 :(得分:3)
覆盖函数调用的最直接方法是使用VxWorks的加载时间链接。请考虑以下来源:
file1.c中:
#include <stdio.h>
int function1 (void);
int function1 ()
{
printf ("function1 called\n");
return 1;
}
file2.c中:
#include <stdio.h>
int function2 (void);
int function2 ()
{
printf ("function2 called\n");
return 2;
}
file3.c中:
int function1 (void);
int function2 (void);
int function3 (void);
int function3 ()
{
function1 ();
function2 ();
return 0;
}
mock.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
->
显然,直接操作内存中的指针会很快变得乏味。此外,内存保护将阻止您更改VxSim中加载的RTP或对象。 (因此,为什么我在实际的硬件上运行它。)我提到了这种可能性,主要是因为它似乎最符合你的问题的陈述。
最后,对于非平凡的单元测试,您可能需要考虑专门为该任务设计的工具。尝试搜索“vxworks单元测试框架”。我没有任何特定工具的丰富经验(并且不想遇到垃圾邮件)。也许,这里的其他人可以提供一个很好的建议。