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 <jpoimboe@redhat.com>
This commit is contained in:
Josh Poimboeuf 2018-06-07 09:31:56 -05:00
parent a8133b1dc3
commit 1330dcc43d
2 changed files with 105 additions and 4 deletions

View File

@ -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)

View File

@ -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"