Skip to content

Commit 7191979

Browse files
committed
srxfixup: detect function symbols at .text offset 0
Add a check for STT_FUNC/STT_NOTYPE symbols at .text value 0, which can become unintended targets of jal 0x0 when called through an export table. New --allow-zero-text flag to bypass the check for modules that have no export table (where jal 0x0 is not a hazard). _start and _ftext are excluded (entry point / linker label, never dispatched via export table). Document the check and flag in README.md.
1 parent 41e0af1 commit 7191979

3 files changed

Lines changed: 114 additions & 2 deletions

File tree

iop/Rules.make

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,8 +145,15 @@ $(IOP_BIN_STRIPPED_ELF): $(IOP_BIN_ELF)
145145
$(DIR_GUARD)
146146
$(IOP_STRIP) --strip-unneeded --remove-section=.pdr --remove-section=.comment --remove-section=.mdebug.abi32 --remove-section=.gnu.attributes -o $@ $<
147147

148+
# If the module has no export table, allow symbols at .text offset 0.
149+
# The jal 0x0 hazard only matters when an export table can dispatch to offset 0.
150+
IOP_SRXFIXUP_FLAGS := --rb --irx1
151+
ifeq ($(filter %exports.o,$(IOP_OBJS)),)
152+
IOP_SRXFIXUP_FLAGS += --allow-zero-text
153+
endif
154+
148155
$(IOP_BIN): $(IOP_BIN_STRIPPED_ELF) $(PS2SDKSRC)/tools/srxfixup/bin/srxfixup
149-
$(PS2SDKSRC)/tools/srxfixup/bin/srxfixup --rb --irx1 -o $@ $<
156+
$(PS2SDKSRC)/tools/srxfixup/bin/srxfixup $(IOP_SRXFIXUP_FLAGS) -o $@ $<
150157

151158
$(IOP_LIB)_tmp$(MAKE_CURPID): $(IOP_OBJS)
152159
$(DIR_GUARD)

tools/srxfixup/README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,23 @@ This tool provides the following:
2626

2727
To see usage and possible command line arguments that can be used with the
2828
program, run it without arguments.
29+
30+
## Zero-.text symbol check
31+
32+
When processing a relocatable ELF (`ET_REL`), `srxfixup` checks for function
33+
or no-type symbols in the `.text` section whose `st_value` is exactly 0. Such
34+
symbols indicate that a tiny stub/thunk object was placed first in `.text` by
35+
the linker. IOP loaders that do not apply `R_MIPS_26` relocations at load
36+
time will leave the corresponding `jal` instructions targeting address 0,
37+
causing an immediate crash (EPC = 0x10).
38+
39+
If any offending symbol is found, `srxfixup` prints an error like:
40+
41+
```
42+
ERROR: foo.irx: symbol 'bar' (sym#3) is in .text with value 0 -- causes jal 0x0; reorder or pad module.
43+
```
44+
45+
and exits with a non-zero status so the build fails.
46+
47+
Use `--allow-zero-text` to suppress the check (e.g. when the caller knows the
48+
loader correctly applies `R_MIPS_26` relocations and accepts the risk).

tools/srxfixup/src/srxfixup.c

Lines changed: 86 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ static unsigned int dispmod_flag = 0;
2626
static int irx1_flag = 0;
2727
static int br_conv = 0;
2828
static int print_config = 0;
29+
static int allow_zero_text = 0;
2930
// clang-format off
3031
static const Opttable opttable[] =
3132
{
@@ -42,6 +43,7 @@ static const Opttable opttable[] =
4243
{ "--rb", ARG_HAVEARG_NONE, 'f', &br_conv },
4344
{ "--relative-branch", ARG_HAVEARG_NONE, 'f', &br_conv },
4445
{ "--print-internal-config", ARG_HAVEARG_NONE, 'f', &print_config },
46+
{ "--allow-zero-text", ARG_HAVEARG_NONE, 'f', &allow_zero_text },
4547
{ NULL, 0, '\0', NULL },
4648
};
4749
static const Opttable stripopttable[] =
@@ -56,13 +58,15 @@ static const Opttable stripopttable[] =
5658
{ "--rb", ARG_HAVEARG_NONE, 'f', &br_conv },
5759
{ "--relative-branch", ARG_HAVEARG_NONE, 'f', &br_conv },
5860
{ "--print-internal-config", ARG_HAVEARG_NONE, 'f', &print_config },
61+
{ "--allow-zero-text", ARG_HAVEARG_NONE, 'f', &allow_zero_text },
5962
{ NULL, 0, '\0', NULL },
6063
};
6164
// clang-format on
6265

6366
static void display_module_info(elf_file *elf);
6467
static void convert_relative_branch_an_section(elf_section *relsect);
6568
static void convert_relative_branch(elf_file *elf);
69+
static int check_zero_text_symbols(const char *source, const elf_file *elf);
6670

6771
void usage(const char *myname)
6872
{
@@ -79,7 +83,8 @@ void usage(const char *myname)
7983
" -o <elf_relocatable_nosymbol_output_file>\n"
8084
" -r <elf_relocatable_output_file>\n"
8185
" -e <entry_point_symbol>\n"
82-
" --relative-branch or --rb\n");
86+
" --relative-branch or --rb\n"
87+
" --allow-zero-text (skip check for .text symbols with value 0)\n");
8388
if ( verbose )
8489
{
8590
printf(" -t <.text start address>\n"
@@ -223,6 +228,10 @@ int main(int argc, char **argv)
223228
{
224229
exit(1);
225230
}
231+
if ( !allow_zero_text && check_zero_text_symbols(source, elf) )
232+
{
233+
exit(1);
234+
}
226235
if (
227236
((elf->ehp->e_flags & EF_MIPS_MACH) == EF_MIPS_MACH_5900)
228237
&& ((elf->ehp->e_flags & EF_MIPS_ARCH) == EF_MIPS_ARCH_3) )
@@ -487,3 +496,79 @@ static void convert_relative_branch(elf_file *elf)
487496
}
488497
}
489498
}
499+
500+
static int check_zero_text_symbols(const char *source, const elf_file *elf)
501+
{
502+
int text_idx;
503+
int i;
504+
int found;
505+
506+
if ( elf->scp == NULL )
507+
{
508+
return 0;
509+
}
510+
511+
/* Find the index of the .text section by name */
512+
text_idx = -1;
513+
for ( i = 1; i < elf->ehp->e_shnum; i += 1 )
514+
{
515+
if ( elf->scp[i]->name && strcmp(elf->scp[i]->name, ".text") == 0 )
516+
{
517+
text_idx = i;
518+
break;
519+
}
520+
}
521+
if ( text_idx < 0 )
522+
{
523+
return 0; /* No .text section — nothing to check */
524+
}
525+
526+
found = 0;
527+
for ( i = 1; i < elf->ehp->e_shnum; i += 1 )
528+
{
529+
elf_section *sect;
530+
unsigned int nsyms;
531+
unsigned int j;
532+
elf_syment **syms;
533+
534+
sect = elf->scp[i];
535+
if ( sect->shr.sh_type != SHT_SYMTAB || sect->data == NULL || sect->shr.sh_entsize == 0 )
536+
{
537+
continue;
538+
}
539+
nsyms = sect->shr.sh_size / sect->shr.sh_entsize;
540+
syms = (elf_syment **)sect->data;
541+
for ( j = 1; j < nsyms; j += 1 ) /* skip sym[0]: ELF null symbol */
542+
{
543+
elf_syment *sym;
544+
545+
sym = syms[j];
546+
/* _start is the IOP module entry point called via the .iopmod
547+
* header, never via a jal from within the module.
548+
* _ftext is a linker label at the start of .text, also never
549+
* the target of an intra-module jal. Both are safe at .text+0. */
550+
if ( sym->name
551+
&& (strcmp(sym->name, "_start") == 0 || strcmp(sym->name, "_ftext") == 0) )
552+
{
553+
continue;
554+
}
555+
if ( (unsigned int)sym->sym.st_shndx == (unsigned int)text_idx
556+
&& (sym->type == STT_FUNC || sym->type == STT_NOTYPE)
557+
&& sym->sym.st_value == 0 )
558+
{
559+
fprintf(
560+
stderr,
561+
"ERROR: %s: symbol '%s' (sym#%u) is in .text with value 0"
562+
" -- if this module has an export table, put exports.o"
563+
" first in IOP_OBJS and move _retonly after"
564+
" DECLARE_EXPORT_TABLE in exports.tab;"
565+
" otherwise use --allow-zero-text.\n",
566+
source,
567+
(sym->name && sym->name[0]) ? sym->name : "<unnamed>",
568+
j);
569+
found = 1;
570+
}
571+
}
572+
}
573+
return found;
574+
}

0 commit comments

Comments
 (0)