aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--tcc.h2
-rw-r--r--tccelf.c37
-rw-r--r--tccgen.c11
-rw-r--r--tests/tests2/86-struct-init.c20
-rw-r--r--tests/tests2/86-struct-init.expect3
5 files changed, 73 insertions, 0 deletions
diff --git a/tcc.h b/tcc.h
index fb104fa..93544f0 100644
--- a/tcc.h
+++ b/tcc.h
@@ -1384,6 +1384,8 @@ ST_FUNC void tcc_add_runtime(TCCState *s1);
ST_FUNC void build_got_entries(TCCState *s1);
ST_FUNC struct sym_attr *get_sym_attr(TCCState *s1, int index, int alloc);
+ST_FUNC void squeeze_multi_relocs(Section *sec, size_t oldrelocoffset);
+
ST_FUNC addr_t get_elf_sym_addr(TCCState *s, const char *name, int err);
#if defined TCC_IS_NATIVE || defined TCC_TARGET_PE
ST_FUNC void *tcc_get_symbol_err(TCCState *s, const char *name);
diff --git a/tccelf.c b/tccelf.c
index c97d742..003797c 100644
--- a/tccelf.c
+++ b/tccelf.c
@@ -535,6 +535,43 @@ ST_FUNC void put_elf_reloc(Section *symtab, Section *s, unsigned long offset,
put_elf_reloca(symtab, s, offset, type, symbol, 0);
}
+/* Remove relocations for section S->reloc starting at oldrelocoffset
+ that are to the same place, retaining the last of them. As side effect
+ the relocations are sorted. Possibly reduces the number of relocs. */
+ST_FUNC void squeeze_multi_relocs(Section *s, size_t oldrelocoffset)
+{
+ Section *sr = s->reloc;
+ ElfW_Rel *r, *dest;
+ ssize_t a;
+ ElfW(Addr) addr;
+
+ if (oldrelocoffset + sizeof(*r) >= sr->data_offset)
+ return;
+ /* The relocs we're dealing with are the result of initializer parsing.
+ So they will be mostly in order and there aren't many of them.
+ Secondly we need a stable sort (which qsort isn't). We use
+ a simple insertion sort. */
+ for (a = oldrelocoffset + sizeof(*r); a < sr->data_offset; a += sizeof(*r)) {
+ ssize_t i = a - sizeof(*r);
+ addr = ((ElfW_Rel*)(sr->data + a))->r_offset;
+ for (; i >= (ssize_t)oldrelocoffset &&
+ ((ElfW_Rel*)(sr->data + i))->r_offset > addr; i -= sizeof(*r)) {
+ ElfW_Rel tmp = *(ElfW_Rel*)(sr->data + a);
+ *(ElfW_Rel*)(sr->data + a) = *(ElfW_Rel*)(sr->data + i);
+ *(ElfW_Rel*)(sr->data + i) = tmp;
+ }
+ }
+
+ r = (ElfW_Rel*)(sr->data + oldrelocoffset);
+ dest = r;
+ for (; r < (ElfW_Rel*)(sr->data + sr->data_offset); r++) {
+ if (dest->r_offset != r->r_offset)
+ dest++;
+ *dest = *r;
+ }
+ sr->data_offset = (unsigned char*)dest - sr->data + sizeof(*r);
+}
+
/* put stab debug information */
ST_FUNC void put_stabs(const char *str, int type, int other, int desc,
diff --git a/tccgen.c b/tccgen.c
index 5c2acf8..5c74971 100644
--- a/tccgen.c
+++ b/tccgen.c
@@ -6019,6 +6019,12 @@ static void init_putv(CType *type, Section *sec, unsigned long c)
continue;
if (rel->r_offset < esym->st_value)
break;
+ /* Note: if the same fields are initialized multiple
+ times (possible with designators) then we possibly
+ add multiple relocations for the same offset here.
+ That would lead to wrong code, the last reloc needs
+ to win. We clean this up later after the whole
+ initializer is parsed. */
put_elf_reloca(symtab_section, sec,
c + rel->r_offset - esym->st_value,
ELFW(R_TYPE)(rel->r_info),
@@ -6602,7 +6608,12 @@ static void decl_initializer_alloc(CType *type, AttributeDef *ad, int r,
vla_sp_loc = addr;
vlas_in_scope++;
} else if (has_init) {
+ size_t oldreloc_offset = 0;
+ if (sec && sec->reloc)
+ oldreloc_offset = sec->reloc->data_offset;
decl_initializer(type, sec, addr, 1, 0);
+ if (sec && sec->reloc)
+ squeeze_multi_relocs(sec, oldreloc_offset);
/* patch flexible array member size back to -1, */
/* for possible subsequent similar declarations */
if (flexible_array)
diff --git a/tests/tests2/86-struct-init.c b/tests/tests2/86-struct-init.c
index 8d27719..fd212ba 100644
--- a/tests/tests2/86-struct-init.c
+++ b/tests/tests2/86-struct-init.c
@@ -199,6 +199,25 @@ void test_compound_with_relocs (void)
p = local_wrap[1].func; p();
}
+void sys_ni(void) { printf("ni\n"); }
+void sys_one(void) { printf("one\n"); }
+void sys_two(void) { printf("two\n"); }
+void sys_three(void) { printf("three\n"); }
+typedef void (*fptr)(void);
+const fptr table[3] = {
+ [0 ... 2] = &sys_ni,
+ [0] = sys_one,
+ [1] = sys_two,
+ [2] = sys_three,
+};
+
+void test_multi_relocs(void)
+{
+ int i;
+ for (i = 0; i < sizeof(table)/sizeof(table[0]); i++)
+ table[i]();
+}
+
int main()
{
print(ce);
@@ -224,5 +243,6 @@ int main()
foo(&gw, &phdr);
//printf("q: %s\n", q);
test_compound_with_relocs();
+ test_multi_relocs();
return 0;
}
diff --git a/tests/tests2/86-struct-init.expect b/tests/tests2/86-struct-init.expect
index 1498a3d..adda76d 100644
--- a/tests/tests2/86-struct-init.expect
+++ b/tests/tests2/86-struct-init.expect
@@ -35,3 +35,6 @@ lv2: 1 2 3 4 68 69 68 69 0 0 0 0 0 0 0 0 0 0 0 0 2f 30
lv3: 7 8 9 a 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 32
lt2: 0 9 9 9 43 43 43 43 42 42 42 0 0 0 0 0 1
flow: 9 8 7 6 0 0 0 0 0 0 0 0 0 0 0 0 6 5 4 3 0 0 0 0 0 0 0 0 0 0 0 0
+one
+two
+three