RISC-V: Also output undefined symbols as dynamic symbols
Checks
| Context |
Check |
Description |
| linaro-tcwg-bot/tcwg_binutils_build--master-arm |
success
|
Build passed
|
| linaro-tcwg-bot/tcwg_binutils_build--master-aarch64 |
success
|
Build passed
|
| linaro-tcwg-bot/tcwg_binutils_check--master-arm |
success
|
Test passed
|
| linaro-tcwg-bot/tcwg_binutils_check--master-aarch64 |
success
|
Test passed
|
Commit Message
Consider this program, which refers to an undefined symbol foo:
int foo(void);
int main() { foo(); }
If linking with --unresolved-symbols=ignore-in-object-file, what should
happen is the symbol foo is left undefined and left for the dynamic
linker to resolve. (See commit 4295841be439 ("RISC-V: Go PLT for
CALL/JUMP/RVC_JUMP if `h->plt.offset' isn't -1").) Instead, with
binutils 2.46:
1. With -no-pie, ld generates an auipc/jalr call to address 0, crashing
at runtime instead of generating an undefined symbol message.
2. With -pie, ld generates this bogus error message (-fPIC does not
change this, and R_RISCV_CALL_PLT is correct here) and stops, failing
to produce an executable:
ld: relocation R_RISCV_CALL_PLT against `foo' which may bind
externally can not be used when making a shared object; recompile
with -fPIC
Bisection leads to commit 9e10fcf71c11 ("RISC-V: Fix the assert fail
when linking discarded sections under -pie for got"), which stopped
outputting dynamic symbols for non-undefweak symbols. Therefore, in
order to fix the issue, also output undefined symbols as dynamic
symbols.
Fixes test case ld-elf/dwarf.exp, specifically dwarf3, where the bogus
error prevents the second "undefined reference" error from appearing. No
regressions found for the ld tests on riscv64.
bfd/:
* elfnn-riscv.c (allocate_dynrelocs): Also output undefined
symbols as dynamic symbols
Fixes: 9e10fcf71c11 ("RISC-V: Fix the assert fail when linking discarded sections under -pie for got")
---
This is my first patch to binutils. Please advise if I'm doing something wrong. Thanks.
Vivian "dramforever" Wang
---
bfd/elfnn-riscv.c | 15 +++++++++------
1 file changed, 9 insertions(+), 6 deletions(-)
Comments
On Mon, May 11, 2026 at 12:28 PM Vivian Wang <wangruikang@iscas.ac.cn> wrote:
>
> Consider this program, which refers to an undefined symbol foo:
>
> int foo(void);
> int main() { foo(); }
>
> If linking with --unresolved-symbols=ignore-in-object-file, what should
> happen is the symbol foo is left undefined and left for the dynamic
> linker to resolve. (See commit 4295841be439 ("RISC-V: Go PLT for
> CALL/JUMP/RVC_JUMP if `h->plt.offset' isn't -1").) Instead, with
> binutils 2.46:
>
> 1. With -no-pie, ld generates an auipc/jalr call to address 0, crashing
> at runtime instead of generating an undefined symbol message.
>
> 2. With -pie, ld generates this bogus error message (-fPIC does not
> change this, and R_RISCV_CALL_PLT is correct here) and stops, failing
> to produce an executable:
>
> ld: relocation R_RISCV_CALL_PLT against `foo' which may bind
> externally can not be used when making a shared object; recompile
> with -fPIC
>
> Bisection leads to commit 9e10fcf71c11 ("RISC-V: Fix the assert fail
> when linking discarded sections under -pie for got"), which stopped
> outputting dynamic symbols for non-undefweak symbols. Therefore, in
> order to fix the issue, also output undefined symbols as dynamic
> symbols.
So do you think we should also stop output dynamic symbols for
undefined symbols? umm maybe you are right, I am not sure. Just
curious, it seems other targets don't need to handle
bfd_link_hash_undefined in the following codes, so why and what's the
difference between riscv and others? I think we might miss some
codes, rather than the codes here, just guess.
Thanks
Nelson
> Fixes test case ld-elf/dwarf.exp, specifically dwarf3, where the bogus
> error prevents the second "undefined reference" error from appearing. No
> regressions found for the ld tests on riscv64.
>
> bfd/:
>
> * elfnn-riscv.c (allocate_dynrelocs): Also output undefined
> symbols as dynamic symbols
>
> Fixes: 9e10fcf71c11 ("RISC-V: Fix the assert fail when linking discarded sections under -pie for got")
> ---
> This is my first patch to binutils. Please advise if I'm doing something wrong. Thanks.
>
> Vivian "dramforever" Wang
> ---
> bfd/elfnn-riscv.c | 15 +++++++++------
> 1 file changed, 9 insertions(+), 6 deletions(-)
>
> diff --git a/bfd/elfnn-riscv.c b/bfd/elfnn-riscv.c
> index eb3d7926c..d66c42b78 100644
> --- a/bfd/elfnn-riscv.c
> +++ b/bfd/elfnn-riscv.c
> @@ -1439,10 +1439,11 @@ allocate_dynrelocs (struct elf_link_hash_entry *h, void *inf)
> && h->plt.refcount > 0)
> {
> /* Make sure this symbol is output as a dynamic symbol.
> - Undefined weak syms won't yet be marked as dynamic. */
> + Undefined and undefweak syms won't yet be marked as dynamic. */
> if (h->dynindx == -1
> && !h->forced_local
> - && h->root.type == bfd_link_hash_undefweak
> + && (h->root.type == bfd_link_hash_undefweak
> + || h->root.type == bfd_link_hash_undefined)
> && !bfd_elf_link_record_dynamic_symbol (info, h))
> return false;
>
> @@ -1500,11 +1501,12 @@ allocate_dynrelocs (struct elf_link_hash_entry *h, void *inf)
> int tls_type = riscv_elf_hash_entry (h)->tls_type;
>
> /* Make sure this symbol is output as a dynamic symbol.
> - Undefined weak syms won't yet be marked as dynamic. */
> + Undefined and undefweak syms won't yet be marked as dynamic. */
> if (dyn
> && h->dynindx == -1
> && !h->forced_local
> - && h->root.type == bfd_link_hash_undefweak
> + && (h->root.type == bfd_link_hash_undefweak
> + || h->root.type == bfd_link_hash_undefined)
> && !bfd_elf_link_record_dynamic_symbol (info, h))
> return false;
>
> @@ -1611,10 +1613,11 @@ allocate_dynrelocs (struct elf_link_hash_entry *h, void *inf)
> || h->root.type == bfd_link_hash_undefined))))
> {
> /* Make sure this symbol is output as a dynamic symbol.
> - Undefined weak syms won't yet be marked as dynamic. */
> + Undefined and undefweak syms won't yet be marked as dynamic. */
> if (h->dynindx == -1
> && !h->forced_local
> - && h->root.type == bfd_link_hash_undefweak
> + && (h->root.type == bfd_link_hash_undefweak
> + || h->root.type == bfd_link_hash_undefined)
> && !bfd_elf_link_record_dynamic_symbol (info, h))
> return false;
>
> --
> 2.53.0
>
On 5/13/26 23:10, Nelson Chu wrote:
> On Mon, May 11, 2026 at 12:28 PM Vivian Wang <wangruikang@iscas.ac.cn> wrote:
>> Consider this program, which refers to an undefined symbol foo:
>>
>> int foo(void);
>> int main() { foo(); }
>>
>> If linking with --unresolved-symbols=ignore-in-object-file, what should
>> happen is the symbol foo is left undefined and left for the dynamic
>> linker to resolve. (See commit 4295841be439 ("RISC-V: Go PLT for
>> CALL/JUMP/RVC_JUMP if `h->plt.offset' isn't -1").) Instead, with
>> binutils 2.46:
>>
>> 1. With -no-pie, ld generates an auipc/jalr call to address 0, crashing
>> at runtime instead of generating an undefined symbol message.
>>
>> 2. With -pie, ld generates this bogus error message (-fPIC does not
>> change this, and R_RISCV_CALL_PLT is correct here) and stops, failing
>> to produce an executable:
>>
>> ld: relocation R_RISCV_CALL_PLT against `foo' which may bind
>> externally can not be used when making a shared object; recompile
>> with -fPIC
>>
>> Bisection leads to commit 9e10fcf71c11 ("RISC-V: Fix the assert fail
>> when linking discarded sections under -pie for got"), which stopped
>> outputting dynamic symbols for non-undefweak symbols. Therefore, in
>> order to fix the issue, also output undefined symbols as dynamic
>> symbols.
> So do you think we should also stop output dynamic symbols for
> undefined symbols? umm maybe you are right, I am not sure.
FWIW, I can't really claim that this handling of undefined symbols is
what *should* be done, just that this restores some slightly earlier
version's behavior for riscv, and gets rid of the bogus error message
from ld.
Maybe I have misunderstood the purpose of the "Go PLT" commit
9e10fcf71c11? Do we just want to get rid of the segfault? In that case
the behavior on at least x86 and arm is to give *this* error message
instead on runtime:
./foo: error while loading shared libraries: unexpected PLT reloc type 0x00
And in the executable, the PLT entry is created but with zeros instead
of actual instructions, the undefined symbol is not emitted in .dynsym,
and probably other stuff are missing as well. So the executable is still
*broken*, but doesn't segfault trying to call address 0.
If that's what we want for riscv then what we want is to allocate a PLT
entry even for an undefined symbol. I think what we need is to fix the
conditional after to be like x86/arm:
diff --git a/bfd/elfnn-riscv.c b/bfd/elfnn-riscv.c
index eb3d7926c..c352e5eba 100644
--- a/bfd/elfnn-riscv.c
+++ b/bfd/elfnn-riscv.c
@@ -1446,7 +1446,8 @@ allocate_dynrelocs (struct elf_link_hash_entry *h, void *inf)
&& !bfd_elf_link_record_dynamic_symbol (info, h))
return false;
- if (WILL_CALL_FINISH_DYNAMIC_SYMBOL (1, bfd_link_pic (info), h))
+ if (bfd_link_pic (info)
+ || WILL_CALL_FINISH_DYNAMIC_SYMBOL (1, 0, h))
{
asection *s = htab->elf.splt;
I'm not sure what the difference is though. The latter first (I think)
appeared back in 2003 (26e415943a5d) and the description was just
"Adjust WILL_CALL_FINISH_DYNAMIC_SYMBOL uses, and optimize." Across BFD
we have 9 instances of if (WILL_CALL_FINISH_DYNAMIC_SYMBOL) and 12
instances of if (bfd_link_pic || WILL_CALL_FINISH_DYNAMIC_SYMBOL), and
some variants like whatever loongarch is doing. But at least I think
this is probably right for riscv.
So, again, that's just an idea. Do you think this fix makes more sense?
> Just
> curious, it seems other targets don't need to handle
> bfd_link_hash_undefined in the following codes, so why and what's the
> difference between riscv and others? I think we might miss some
> codes, rather than the codes here, just guess.
I don't think I missed anything for *this* case, since it was three
occurrences of the same code and same comments. There may be other
situations regarding undefined and undefweak that are not handled. But
also, as said above, the approach could be wrong.
Vivian "dramforever" Wang
> [...]
@@ -1439,10 +1439,11 @@ allocate_dynrelocs (struct elf_link_hash_entry *h, void *inf)
&& h->plt.refcount > 0)
{
/* Make sure this symbol is output as a dynamic symbol.
- Undefined weak syms won't yet be marked as dynamic. */
+ Undefined and undefweak syms won't yet be marked as dynamic. */
if (h->dynindx == -1
&& !h->forced_local
- && h->root.type == bfd_link_hash_undefweak
+ && (h->root.type == bfd_link_hash_undefweak
+ || h->root.type == bfd_link_hash_undefined)
&& !bfd_elf_link_record_dynamic_symbol (info, h))
return false;
@@ -1500,11 +1501,12 @@ allocate_dynrelocs (struct elf_link_hash_entry *h, void *inf)
int tls_type = riscv_elf_hash_entry (h)->tls_type;
/* Make sure this symbol is output as a dynamic symbol.
- Undefined weak syms won't yet be marked as dynamic. */
+ Undefined and undefweak syms won't yet be marked as dynamic. */
if (dyn
&& h->dynindx == -1
&& !h->forced_local
- && h->root.type == bfd_link_hash_undefweak
+ && (h->root.type == bfd_link_hash_undefweak
+ || h->root.type == bfd_link_hash_undefined)
&& !bfd_elf_link_record_dynamic_symbol (info, h))
return false;
@@ -1611,10 +1613,11 @@ allocate_dynrelocs (struct elf_link_hash_entry *h, void *inf)
|| h->root.type == bfd_link_hash_undefined))))
{
/* Make sure this symbol is output as a dynamic symbol.
- Undefined weak syms won't yet be marked as dynamic. */
+ Undefined and undefweak syms won't yet be marked as dynamic. */
if (h->dynindx == -1
&& !h->forced_local
- && h->root.type == bfd_link_hash_undefweak
+ && (h->root.type == bfd_link_hash_undefweak
+ || h->root.type == bfd_link_hash_undefined)
&& !bfd_elf_link_record_dynamic_symbol (info, h))
return false;