用函数B覆盖一个弱函数A

5

对于一个嵌入式设备,我有一个包含函数指针数组的文件,存储着中断处理程序,定义如下(无法修改):

typedef void (*const ISRFunction)(void);

__attribute__((weak)) void ISR0(void){ for(;;); }
__attribute__((weak)) void ISR1(void){ for(;;); }
...
__attribute__((weak)) void ISR78(void){ for(;;); }
...

ISRFunction __vector_table[0x79] = 
{
    (ISRFunction)&ISR0,
    (ISRFunction)&ISR1,
    ...
    (ISRFunction)&ISR78,
    ...
}

我有第二个文件,其中定义了一些函数,但我无法修改。这个文件的结构如下:

void blinkLed(void)
{ ... }

最后,我有一个主源文件,其中包含main函数和设备配置。在中断78上,我想要闪烁LED。因此,我编写了一个强大的ISR78函数,如下:

void ISR78(void)
{
    blinkLed();
}

我想知道是否有一种解决方案,可以直接通过blinkLed来覆盖弱函数ISR78,即在__vector_table中存储blinkLed的地址而不修改或重命名函数?

编辑:

我实际上使用GNU gcc 4.9.3和相关联的链接器(GNU ld 2.24.0)。我可以修改与项目相关的main.c和Makefile。


一些编译器(不知道你的是否支持)支持符号别名,这可能是值得研究的东西。 - user694733
此外,您应该非常明确地了解函数类型:void func(void)(没有参数)和 void func()(未指定数量的参数)是不同的类型。对于没有参数的函数,请始终使用 void 来避免问题。 - user694733
@user694733 谢谢你的建议,我会注意的。 - Garf365
也许我没有全球化的问题视野,但“weak”是设计用来允许“覆盖”函数的,对吗?因此,你可以定义/实现你的_same_name_function_来覆盖这个weak函数。 - LPs
@LPs 我已经做过了。我使用一个_same_name_function_作为包装器来覆盖weak函数,以执行真正的工作。我想知道是否有一种直接用执行真正工作的函数来覆盖weak函数的方法。 - Garf365
3个回答

2
简而言之:你已经有一个可行的解决方案,这个解决方案似乎是使用弱符号明确支持和鼓励的解决方案。你希望从不同的解决方案中获得什么改进?
链接器符号按名称查找,因此使用预期名称之外的唯一选择是:
1.直接修改链接步骤(tofro提出的建议)。
2.自己修改函数指针表。
首先使用弱符号将ISR78变为允许覆盖(通过符号名称)你已经使用的方式。
我无法看到修改中断向量表比直接使用预期函数名称更好的方法,即使可能存在也是如此。

我能理解OP想要删除那一个间接寻址(这通常会评估为一条单独的分支指令) - 在某些问题中,每个周期都非常重要。 - tofro
哪一个间接寻址?您正在通过表进行调用,链接器将在链接时修复该表 - 包括在弱符号和其他符号之间进行选择。无论您选择其中哪个,在函数调用时间都没有影响(除非您使用动态链接并且至少有一个跳板函数)。 - Useless

2
我唯一看到实现你想做的事情的方法是通过修补包含blink符号的目标文件的符号表,用ISR78符号替换它。使用以下命令:

objcopy [...] --redefine-sym blink=ISR78

链接器应该会自动将前一个blink地址插入向量表中。显然,在此之后,你的blink符号就消失了,不应从其他地方调用。

然而,我认为这是一种hack方法。

如果_vector_table在全局可访问且位于可写内存中(不要假设,那可能太简单了……),你可以通过自己的代码对其进行修补:

_vector_table [0x78] = blink;

在运行时。

我会查看这个方法。我更喜欢带有__attribute__或任何特定或通用的C代码或链接器(链接器选项或链接器脚本)的方法,但这已经是第一种方法了。我同意你将其视为hack。此外,如果在其他地方使用blink可能会很危险(但因为它管理ISR,所以不应该在其他地方调用)。 - Garf365
@Garf365 或许你可以使用 ld--defsym 命令,并带上类似于 ISR78=blink 的参数(或者是反过来?)- 不过我自己从未尝试过这种方法,也不太清楚这样做会有什么其他影响,或者它是否能够正常工作。 - tofro
最后,这并不是我想要的答案,但它似乎是唯一可能的答案。非常感谢! - Garf365
我没有尝试你的最后一个建议,因为__vector_table在只读内存中...但是谢谢,如果你将__vector_table移到RAM中,我会记住这个建议的 - Garf365
@Garf365 实际上,我所说的“最后建议”是使用ld --defsym ISR78=blink - tofro
显示剩余2条评论

1
你可以使用__asm("symbolname")来实现这个功能。这样可以将目标文件中使用的符号名称更改为特定的字符串。gcc和clang都支持这个功能。
示例:
void blinkLed(void) __asm("ISR78");  // Can only be used on a prototype
void blinkLed(void)
{ ... }

在你的代码中,一切都可以使用名称blinkLed。但在目标文件中,名称将会是ISR78,它将覆盖弱版本并作为中断向量使用。
这也可以作为一种避免C++名称混淆的方法,即使你不能使用extern "C"
template <int id>
struct Timer {
   static void blinkled();
};

template <> Timer<1>::blinkled() __asm("ISR71");
template <> Timer<2>::blinkled() __asm("ISR72");

上面我们有一个带有ISR(中断服务程序)的类模板,blinkled()作为一个静态方法。然后我们为两个不同的定时器定义了两个ISR的特化,并为每个中断向量给出了正确的名称。

真是一个有趣的解决方案,可惜我不能修改包含blinkLed的头文件/源文件,所以无法改变原型,但我会把这种方式用到另一个项目上!特别是用来处理C++的名称修饰。 - undefined
1
你仍然可以使用它。一个函数的原型可以写多次,只有当它们冲突时才会出错。原型中的__asm()是可累加的。将其放在一个原型中会改变名称,而在另一个原型中省略它并不会撤销名称更改。因此,你可以在自己的头文件中添加它,然后在定义函数的源文件中包含该头文件,这样就不会与现有头文件中的原型冲突。 - undefined
1
请记住,ISR(中断服务程序)是特殊的函数。通常在声明它们时需要使用类似于__attribute__((interrupt))__attribute__((signal))的语法,以生成特殊的代码。普通函数不能直接成为ISR。 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接