From ddd461dcc866d17f1c89137dc275ce1da95e7be2 Mon Sep 17 00:00:00 2001 From: Michael Matz Date: Sat, 8 Oct 2016 02:44:17 +0200 Subject: [PATCH] Fix initializing members multiple times When intializing members where the initializer needs relocations and the member is initialized multiple times we can't allow that to lead to multiple relocations to the same place. The last one must win. --- tcc.h | 2 ++ tccelf.c | 37 ++++++++++++++++++++++++++++++ tccgen.c | 11 +++++++++ tests/tests2/86-struct-init.c | 20 ++++++++++++++++ tests/tests2/86-struct-init.expect | 3 +++ 5 files changed, 73 insertions(+) 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