1

我的问题如下。我有一个由各种 c 文件组成的代码单元,例如

  • 文件1.c
  • 文件2.c
  • 文件3.c

所有这些都用 GCC 编译成一个唯一的对象“object.o”,然后将其与其他对象链接,最后给出在 VxWorks 上运行的可执行文件“application.out”。

由于我正在对“object.o”进行单元测试,因此我需要通过代码激发所有可能的方式。具体来说,在某些情况下,为了模拟错误发生,我应该执行模拟函数而不是原始函数。例如,假设有一个我正在尝试测试的函数“func_caller”,在执行的某个时刻,它调用了另一个函数“func_call”(声明为静态)。

由于我不想修改原始代码,我想知道是否有一种方法可以操纵指令指针,使得当调用“func_call”时,它实际上执行另一个模拟函数“func_called_mock”和调用者“func_caller " 什么都没有注意到。

提前致谢。

4

1 回答 1

3

覆盖函数调用的最直接方法是使用 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 单元测试框架”。我对任何特定工具都没有丰富的经验(也不想遇到垃圾邮件)。也许,这里的其他人可以提供一个很好的建议。

于 2013-06-27T21:24:54.327 回答