From 1330dcc43d3e0e2adf8a1720610492d06d1d4cfe Mon Sep 17 00:00:00 2001 From: Josh Poimboeuf Date: Thu, 7 Jun 2018 09:31:56 -0500 Subject: [PATCH] create-diff-object: add ORC section support Finally add support for processing the ORC unwinder sections. The ORC unwinder sections are more special than the other special sections, so they need their own dedicated function to process them, though the code is similar to kpatch_regenerate_special_sections(). BTW, upstream livepatch still doesn't support the ORC unwinder. That change will be coming soon (probably Linux 4.19). Fixes #785. Signed-off-by: Josh Poimboeuf --- kpatch-build/create-diff-object.c | 90 +++++++++++++++++++++++++++++++ kpatch-build/kpatch-build | 19 +++++-- 2 files changed, 105 insertions(+), 4 deletions(-) diff --git a/kpatch-build/create-diff-object.c b/kpatch-build/create-diff-object.c index e430a8d..72cd44b 100644 --- a/kpatch-build/create-diff-object.c +++ b/kpatch-build/create-diff-object.c @@ -1998,6 +1998,94 @@ static void kpatch_regenerate_special_section(struct kpatch_elf *kelf, sec->base->data->d_size = dest_offset; } +#define ORC_IP_PTR_SIZE 4 + +/* + * This function is similar to kpatch_regenerate_special_section(), but + * customized for the ORC-related sections. ORC is more special than the other + * special sections because each ORC entry is split into .orc_unwind (struct + * orc_entry) and .orc_unwind_ip. + */ +static void kpatch_regenerate_orc_sections(struct kpatch_elf *kelf) +{ + struct rela *rela, *safe; + char *src, *dest, *str; + unsigned int src_idx = 0, dest_idx = 0, orc_entry_size; + struct section *orc_sec, *ip_sec; + + + str = getenv("ORC_STRUCT_SIZE"); + if (!str) + return; + orc_entry_size = atoi(str); + + LIST_HEAD(newrelas); + + orc_sec = find_section_by_name(&kelf->sections, ".orc_unwind"); + ip_sec = find_section_by_name(&kelf->sections, ".orc_unwind_ip"); + + if (!orc_sec || !ip_sec) + return; + + if (orc_sec->sh.sh_size % orc_entry_size != 0) + ERROR("bad .orc_unwind size"); + + if (ip_sec->sh.sh_size != + (orc_sec->sh.sh_size / orc_entry_size) * ORC_IP_PTR_SIZE) + ERROR(".orc_unwind/.orc_unwind_ip size mismatch"); + + src = orc_sec->data->d_buf; + dest = malloc(orc_sec->sh.sh_size); + if (!dest) + ERROR("malloc"); + + list_for_each_entry_safe(rela, safe, &ip_sec->rela->relas, list) { + + if (rela->sym->type != STT_FUNC || !rela->sym->sec->include) + goto next; + + /* copy orc entry */ + memcpy(dest + (dest_idx * orc_entry_size), + src + (src_idx * orc_entry_size), + orc_entry_size); + + /* move ip rela */ + list_del(&rela->list); + list_add_tail(&rela->list, &newrelas); + rela->offset = dest_idx * ORC_IP_PTR_SIZE; + rela->sym->include = 1; + + dest_idx++; +next: + src_idx++; + } + + if (!dest_idx) { + /* no changed or global functions referenced */ + orc_sec->status = ip_sec->status = ip_sec->rela->status = SAME; + orc_sec->include = ip_sec->include = ip_sec->rela->include = 0; + free(dest); + return; + } + + /* overwrite with new relas list */ + list_replace(&newrelas, &ip_sec->rela->relas); + + /* include the sections */ + orc_sec->include = ip_sec->include = ip_sec->rela->include = 1; + + /* + * Update data buf/size. + * + * The ip section can keep its old (zeroed data), though its size has + * possibly decreased. The ip rela section's data buf and size will be + * regenerated in kpatch_rebuild_rela_section_data(). + */ + orc_sec->data->d_buf = dest; + orc_sec->data->d_size = dest_idx * orc_entry_size; + ip_sec->data->d_size = dest_idx * ORC_IP_PTR_SIZE; +} + static void kpatch_check_relocations(struct kpatch_elf *kelf) { struct rela *rela; @@ -2278,6 +2366,8 @@ static void kpatch_process_special_sections(struct kpatch_elf *kelf) sec->rela->include = 0; } } + + kpatch_regenerate_orc_sections(kelf); } static struct sym_compare_type *kpatch_elf_locals(struct kpatch_elf *kelf) diff --git a/kpatch-build/kpatch-build b/kpatch-build/kpatch-build index ff5ae5c..10fc1bf 100755 --- a/kpatch-build/kpatch-build +++ b/kpatch-build/kpatch-build @@ -260,30 +260,35 @@ find_special_section_data() { fi [[ "$CONFIG_PARAVIRT" -eq 0 ]] && AWK_OPTIONS="-vskip_p=1" + [[ "$CONFIG_UNWINDER_ORC" -eq 0 ]] && AWK_OPTIONS="$AWK_OPTIONS -vskip_o=1" + SPECIAL_VARS="$(readelf -wi "$VMLINUX" | - gawk --non-decimal-data $AWK_OPTIONS ' - BEGIN { a = b = p = e = 0 } + gawk --non-decimal-data "$AWK_OPTIONS" ' + BEGIN { a = b = p = e = o = 0 } # Set state if name matches a == 0 && /DW_AT_name.* alt_instr[[:space:]]*$/ {a = 1; next} b == 0 && /DW_AT_name.* bug_entry[[:space:]]*$/ {b = 1; next} p == 0 && /DW_AT_name.* paravirt_patch_site[[:space:]]*$/ {p = 1; next} e == 0 && /DW_AT_name.* exception_table_entry[[:space:]]*$/ {e = 1; next} + o == 0 && /DW_AT_name.* orc_entry[[:space:]]*$/ {o = 1; next} # Reset state unless this abbrev describes the struct size a == 1 && !/DW_AT_byte_size/ { a = 0; next } b == 1 && !/DW_AT_byte_size/ { b = 0; next } p == 1 && !/DW_AT_byte_size/ { p = 0; next } e == 1 && !/DW_AT_byte_size/ { e = 0; next } + o == 1 && !/DW_AT_byte_size/ { o = 0; next } # Now that we know the size, stop parsing for it a == 1 {printf("export ALT_STRUCT_SIZE=%d\n", $4); a = 2} b == 1 {printf("export BUG_STRUCT_SIZE=%d\n", $4); b = 2} p == 1 {printf("export PARA_STRUCT_SIZE=%d\n", $4); p = 2} e == 1 {printf("export EX_STRUCT_SIZE=%d\n", $4); e = 2} + o == 1 {printf("export ORC_STRUCT_SIZE=%d\n", $4); o = 2} # Bail out once we have everything - a == 2 && b == 2 && (p == 2 || skip_p) && e == 2 {exit}')" + a == 2 && b == 2 && (p == 2 || skip_p) && e == 2 && (o == 2 || skip_o) {exit}')" [[ -n "$SPECIAL_VARS" ]] && eval "$SPECIAL_VARS" @@ -291,6 +296,7 @@ find_special_section_data() { [[ -z "$BUG_STRUCT_SIZE" ]] && die "can't find special struct bug_entry size" [[ -z "$EX_STRUCT_SIZE" ]] && die "can't find special struct paravirt_patch_site size" [[ -z "$PARA_STRUCT_SIZE" && "$CONFIG_PARAVIRT" -ne 0 ]] && die "can't find special struct paravirt_patch_site size" + [[ -z "$ORC_STRUCT_SIZE" && "$CONFIG_UNWINDER_ORC" -ne 0 ]] && die "can't find special struct orc_entry size" return } @@ -667,12 +673,17 @@ else KBUILD_EXTRA_SYMBOLS="$SYMVERSFILE" fi -# optional kernel configs: CONFIG_PARAVIRT +# optional kernel configs: if grep -q "CONFIG_PARAVIRT=y" "$CONFIGFILE"; then CONFIG_PARAVIRT=1 else CONFIG_PARAVIRT=0 fi +if grep -q "CONFIG_UNWINDER_ORC=y" "$CONFIGFILE"; then + CONFIG_UNWINDER_ORC=1 +else + CONFIG_UNWINDER_ORC=0 +fi # unsupported kernel option checking: CONFIG_DEBUG_INFO_SPLIT grep -q "CONFIG_DEBUG_INFO_SPLIT=y" "$CONFIGFILE" && die "kernel option 'CONFIG_DEBUG_INFO_SPLIT' not supported"