mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-07-22 09:17:30 -04:00
RISC-V uses -fpatchable-function-entry=8,4 when the compressed ISA is
enabled and -fpatchable-function-entry=4,2 otherwise. In both cases, the
patchable NOP area starts 8 bytes before the function symbol address.
The __mcount_loc entries therefore point at the patchable NOP area
associated with a function, while nm reports the function symbol at the
entry address used for the function range check.
After RISC-V selected HAVE_BUILDTIME_MCOUNT_SORT, sorttable started
applying that range check at build time. Without allowing entries just
before the reported function address, the mcount sorter treats valid
RISC-V ftrace callsites as invalid weak-function entries and writes
them back as zero. The resulting kernel boots with no ftrace entries,
breaking dynamic ftrace and users such as livepatch.
The failure is silent during the final link because zeroing weak-function
entries is an expected sorttable operation. At boot, those zero entries
are skipped by ftrace_process_locs(), so the only obvious symptom is that
the vmlinux ftrace table has lost valid callsites and ftrace users cannot
attach to them.
CONFIG_FTRACE_SORT_STARTUP_TEST also reports the table as sorted in this
state: it only checks that the __mcount_loc entries are in ascending
order, which a fully zeroed table trivially satisfies. The original
commit relied on this check and did not see the regression.
On an affected RISC-V QEMU boot with both CONFIG_FTRACE_SORT_STARTUP_TEST
and CONFIG_FTRACE_STARTUP_TEST enabled, the sort check still passes
while ftrace reports zero usable entries and the early selftests fail:
[ 0.000000] ftrace section at ffffffff8101da98 sorted properly
[ 0.000000] ftrace: allocating 0 entries in 128 pages
[ 0.054999] Testing tracer function: .. no entries found ..FAILED!
[ 0.172407] tracer: function failed selftest, disabling
[ 0.178186] Failed to init function_graph tracer, init returned -19
Handle RISC-V like arm64 for the function-range check and allow
patchable entries up to 8 bytes before the function address.
With this fix, a RISC-V QEMU smoke boot with ftrace startup tests shows
the vmlinux ftrace table is populated and dynamic ftrace still works:
[ 0.000000] ftrace: allocating 46749 entries in 184 pages
[ 0.051115] Testing tracer function: PASSED
[ 1.283782] Testing dynamic ftrace: PASSED
[ 6.275456] Testing tracer function_graph: PASSED
Fixes: 0ca1724b56af ("riscv: ftrace: select HAVE_BUILDTIME_MCOUNT_SORT")
Suggested-by: Steven Rostedt (Google) <rostedt@goodmis.org>
Reviewed-by: Steven Rostedt <rostedt@goodmis.org>
Reviewed-by: Shuai Xue <xueshuai@linux.alibaba.com>
Reviewed-by: Chen Pei <cp0613@linux.alibaba.com>
Link: https://lore.kernel.org/all/20260527113028.4b21a5de@fedora/
Signed-off-by: Wang Han <wanghan@linux.alibaba.com>
Reviewed-by: Martin Kaiser <martin@kaiser.cx>
Link: https://patch.msgid.link/20260609063002.3943001-1-wanghan@linux.alibaba.com
Signed-off-by: Paul Walmsley <pjw@kernel.org>
1011 lines
24 KiB
C
1011 lines
24 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* sorttable.c: Sort the kernel's table
|
|
*
|
|
* Added ORC unwind tables sort support and other updates:
|
|
* Copyright (C) 1999-2019 Alibaba Group Holding Limited. by:
|
|
* Shile Zhang <shile.zhang@linux.alibaba.com>
|
|
*
|
|
* Copyright 2011 - 2012 Cavium, Inc.
|
|
*
|
|
* Based on code taken from recortmcount.c which is:
|
|
*
|
|
* Copyright 2009 John F. Reiser <jreiser@BitWagon.com>. All rights reserved.
|
|
*
|
|
* Restructured to fit Linux format, as well as other updates:
|
|
* Copyright 2010 Steven Rostedt <srostedt@redhat.com>, Red Hat Inc.
|
|
*/
|
|
|
|
/*
|
|
* Strategy: alter the vmlinux file in-place.
|
|
*/
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <getopt.h>
|
|
#include <fcntl.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <stdbool.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <errno.h>
|
|
#include <pthread.h>
|
|
|
|
#include "elf-parse.h"
|
|
|
|
#ifndef EM_ARCOMPACT
|
|
#define EM_ARCOMPACT 93
|
|
#endif
|
|
|
|
#ifndef EM_XTENSA
|
|
#define EM_XTENSA 94
|
|
#endif
|
|
|
|
#ifndef EM_AARCH64
|
|
#define EM_AARCH64 183
|
|
#endif
|
|
|
|
#ifndef EM_MICROBLAZE
|
|
#define EM_MICROBLAZE 189
|
|
#endif
|
|
|
|
#ifndef EM_ARCV2
|
|
#define EM_ARCV2 195
|
|
#endif
|
|
|
|
#ifndef EM_RISCV
|
|
#define EM_RISCV 243
|
|
#endif
|
|
|
|
#ifndef EM_LOONGARCH
|
|
#define EM_LOONGARCH 258
|
|
#endif
|
|
|
|
typedef void (*table_sort_t)(char *, int);
|
|
|
|
/*
|
|
* Move reserved section indices SHN_LORESERVE..SHN_HIRESERVE out of
|
|
* the way to -256..-1, to avoid conflicting with real section
|
|
* indices.
|
|
*/
|
|
#define SPECIAL(i) ((i) - (SHN_HIRESERVE + 1))
|
|
|
|
static inline int is_shndx_special(unsigned int i)
|
|
{
|
|
return i != SHN_XINDEX && i >= SHN_LORESERVE && i <= SHN_HIRESERVE;
|
|
}
|
|
|
|
/* Accessor for sym->st_shndx, hides ugliness of "64k sections" */
|
|
static inline unsigned int get_secindex(unsigned int shndx,
|
|
unsigned int sym_offs,
|
|
const Elf32_Word *symtab_shndx_start)
|
|
{
|
|
if (is_shndx_special(shndx))
|
|
return SPECIAL(shndx);
|
|
if (shndx != SHN_XINDEX)
|
|
return shndx;
|
|
return elf_parser.r(&symtab_shndx_start[sym_offs]);
|
|
}
|
|
|
|
static int compare_extable_32(const void *a, const void *b)
|
|
{
|
|
Elf32_Addr av = elf_parser.r(a);
|
|
Elf32_Addr bv = elf_parser.r(b);
|
|
|
|
if (av < bv)
|
|
return -1;
|
|
return av > bv;
|
|
}
|
|
|
|
static int compare_extable_64(const void *a, const void *b)
|
|
{
|
|
Elf64_Addr av = elf_parser.r8(a);
|
|
Elf64_Addr bv = elf_parser.r8(b);
|
|
|
|
if (av < bv)
|
|
return -1;
|
|
return av > bv;
|
|
}
|
|
|
|
static int (*compare_extable)(const void *a, const void *b);
|
|
|
|
static inline void *get_index(void *start, int entsize, int index)
|
|
{
|
|
return start + (entsize * index);
|
|
}
|
|
|
|
static int extable_ent_size;
|
|
static int long_size;
|
|
|
|
#define ERRSTR_MAXSZ 256
|
|
|
|
#ifdef UNWINDER_ORC_ENABLED
|
|
/* ORC unwinder only support X86_64 */
|
|
#include <asm/orc_types.h>
|
|
|
|
static char g_err[ERRSTR_MAXSZ];
|
|
static int *g_orc_ip_table;
|
|
static struct orc_entry *g_orc_table;
|
|
|
|
static pthread_t orc_sort_thread;
|
|
|
|
static inline unsigned long orc_ip(const int *ip)
|
|
{
|
|
return (unsigned long)ip + *ip;
|
|
}
|
|
|
|
static int orc_sort_cmp(const void *_a, const void *_b)
|
|
{
|
|
struct orc_entry *orc_a, *orc_b;
|
|
const int *a = g_orc_ip_table + *(int *)_a;
|
|
const int *b = g_orc_ip_table + *(int *)_b;
|
|
unsigned long a_val = orc_ip(a);
|
|
unsigned long b_val = orc_ip(b);
|
|
|
|
if (a_val > b_val)
|
|
return 1;
|
|
if (a_val < b_val)
|
|
return -1;
|
|
|
|
/*
|
|
* The "weak" section terminator entries need to always be on the left
|
|
* to ensure the lookup code skips them in favor of real entries.
|
|
* These terminator entries exist to handle any gaps created by
|
|
* whitelisted .o files which didn't get objtool generation.
|
|
*/
|
|
orc_a = g_orc_table + (a - g_orc_ip_table);
|
|
orc_b = g_orc_table + (b - g_orc_ip_table);
|
|
if (orc_a->type == ORC_TYPE_UNDEFINED && orc_b->type == ORC_TYPE_UNDEFINED)
|
|
return 0;
|
|
return orc_a->type == ORC_TYPE_UNDEFINED ? -1 : 1;
|
|
}
|
|
|
|
static void *sort_orctable(void *arg)
|
|
{
|
|
int i;
|
|
int *idxs = NULL;
|
|
int *tmp_orc_ip_table = NULL;
|
|
struct orc_entry *tmp_orc_table = NULL;
|
|
unsigned int *orc_ip_size = (unsigned int *)arg;
|
|
unsigned int num_entries = *orc_ip_size / sizeof(int);
|
|
unsigned int orc_size = num_entries * sizeof(struct orc_entry);
|
|
|
|
idxs = (int *)malloc(*orc_ip_size);
|
|
if (!idxs) {
|
|
snprintf(g_err, ERRSTR_MAXSZ, "malloc idxs: %s",
|
|
strerror(errno));
|
|
pthread_exit(g_err);
|
|
}
|
|
|
|
tmp_orc_ip_table = (int *)malloc(*orc_ip_size);
|
|
if (!tmp_orc_ip_table) {
|
|
snprintf(g_err, ERRSTR_MAXSZ, "malloc tmp_orc_ip_table: %s",
|
|
strerror(errno));
|
|
pthread_exit(g_err);
|
|
}
|
|
|
|
tmp_orc_table = (struct orc_entry *)malloc(orc_size);
|
|
if (!tmp_orc_table) {
|
|
snprintf(g_err, ERRSTR_MAXSZ, "malloc tmp_orc_table: %s",
|
|
strerror(errno));
|
|
pthread_exit(g_err);
|
|
}
|
|
|
|
/* initialize indices array, convert ip_table to absolute address */
|
|
for (i = 0; i < num_entries; i++) {
|
|
idxs[i] = i;
|
|
tmp_orc_ip_table[i] = g_orc_ip_table[i] + i * sizeof(int);
|
|
}
|
|
memcpy(tmp_orc_table, g_orc_table, orc_size);
|
|
|
|
qsort(idxs, num_entries, sizeof(int), orc_sort_cmp);
|
|
|
|
for (i = 0; i < num_entries; i++) {
|
|
if (idxs[i] == i)
|
|
continue;
|
|
|
|
/* convert back to relative address */
|
|
g_orc_ip_table[i] = tmp_orc_ip_table[idxs[i]] - i * sizeof(int);
|
|
g_orc_table[i] = tmp_orc_table[idxs[i]];
|
|
}
|
|
|
|
free(idxs);
|
|
free(tmp_orc_ip_table);
|
|
free(tmp_orc_table);
|
|
pthread_exit(NULL);
|
|
}
|
|
#endif
|
|
|
|
#ifdef MCOUNT_SORT_ENABLED
|
|
|
|
static int compare_values_64(const void *a, const void *b)
|
|
{
|
|
uint64_t av = *(uint64_t *)a;
|
|
uint64_t bv = *(uint64_t *)b;
|
|
|
|
if (av < bv)
|
|
return -1;
|
|
return av > bv;
|
|
}
|
|
|
|
static int compare_values_32(const void *a, const void *b)
|
|
{
|
|
uint32_t av = *(uint32_t *)a;
|
|
uint32_t bv = *(uint32_t *)b;
|
|
|
|
if (av < bv)
|
|
return -1;
|
|
return av > bv;
|
|
}
|
|
|
|
static int (*compare_values)(const void *a, const void *b);
|
|
|
|
/* Only used for sorting mcount table */
|
|
static void rela_write_addend(Elf_Rela *rela, uint64_t val)
|
|
{
|
|
elf_parser.rela_write_addend(rela, val);
|
|
}
|
|
|
|
struct func_info {
|
|
uint64_t addr;
|
|
uint64_t size;
|
|
};
|
|
|
|
/* List of functions created by: nm -S vmlinux */
|
|
static struct func_info *function_list;
|
|
static int function_list_size;
|
|
|
|
/* Allocate functions in 1k blocks */
|
|
#define FUNC_BLK_SIZE 1024
|
|
#define FUNC_BLK_MASK (FUNC_BLK_SIZE - 1)
|
|
|
|
static int add_field(uint64_t addr, uint64_t size)
|
|
{
|
|
struct func_info *fi;
|
|
int fsize = function_list_size;
|
|
|
|
if (!(fsize & FUNC_BLK_MASK)) {
|
|
fsize += FUNC_BLK_SIZE;
|
|
fi = realloc(function_list, fsize * sizeof(struct func_info));
|
|
if (!fi)
|
|
return -1;
|
|
function_list = fi;
|
|
}
|
|
fi = &function_list[function_list_size++];
|
|
fi->addr = addr;
|
|
fi->size = size;
|
|
return 0;
|
|
}
|
|
|
|
/* Used for when mcount/fentry is before the function entry */
|
|
static int before_func;
|
|
|
|
/* Only return match if the address lies inside the function size */
|
|
static int cmp_func_addr(const void *K, const void *A)
|
|
{
|
|
uint64_t key = *(const uint64_t *)K;
|
|
const struct func_info *a = A;
|
|
|
|
if (key + before_func < a->addr)
|
|
return -1;
|
|
return key >= a->addr + a->size;
|
|
}
|
|
|
|
/* Find the function in function list that is bounded by the function size */
|
|
static int find_func(uint64_t key)
|
|
{
|
|
return bsearch(&key, function_list, function_list_size,
|
|
sizeof(struct func_info), cmp_func_addr) != NULL;
|
|
}
|
|
|
|
static int cmp_funcs(const void *A, const void *B)
|
|
{
|
|
const struct func_info *a = A;
|
|
const struct func_info *b = B;
|
|
|
|
if (a->addr < b->addr)
|
|
return -1;
|
|
return a->addr > b->addr;
|
|
}
|
|
|
|
static int parse_symbols(const char *fname)
|
|
{
|
|
FILE *fp;
|
|
char addr_str[20]; /* Only need 17, but round up to next int size */
|
|
char size_str[20];
|
|
char type;
|
|
|
|
fp = fopen(fname, "r");
|
|
if (!fp) {
|
|
perror(fname);
|
|
return -1;
|
|
}
|
|
|
|
while (fscanf(fp, "%16s %16s %c %*s\n", addr_str, size_str, &type) == 3) {
|
|
uint64_t addr;
|
|
uint64_t size;
|
|
|
|
/* Only care about functions */
|
|
if (type != 't' && type != 'T' && type != 'W')
|
|
continue;
|
|
|
|
addr = strtoull(addr_str, NULL, 16);
|
|
size = strtoull(size_str, NULL, 16);
|
|
if (add_field(addr, size) < 0)
|
|
return -1;
|
|
}
|
|
fclose(fp);
|
|
|
|
qsort(function_list, function_list_size, sizeof(struct func_info), cmp_funcs);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static pthread_t mcount_sort_thread;
|
|
static bool sort_reloc;
|
|
|
|
static long rela_type;
|
|
|
|
static char m_err[ERRSTR_MAXSZ];
|
|
|
|
struct elf_mcount_loc {
|
|
Elf_Ehdr *ehdr;
|
|
Elf_Shdr *init_data_sec;
|
|
uint64_t start_mcount_loc;
|
|
uint64_t stop_mcount_loc;
|
|
};
|
|
|
|
/* Fill the array with the content of the relocs */
|
|
static int fill_relocs(void *ptr, uint64_t size, Elf_Ehdr *ehdr, uint64_t start_loc)
|
|
{
|
|
Elf_Shdr *shdr_start;
|
|
Elf_Rela *rel;
|
|
unsigned int shnum;
|
|
unsigned int count = 0;
|
|
int shentsize;
|
|
void *array_end = ptr + size;
|
|
|
|
shdr_start = (Elf_Shdr *)((char *)ehdr + ehdr_shoff(ehdr));
|
|
shentsize = ehdr_shentsize(ehdr);
|
|
|
|
shnum = ehdr_shnum(ehdr);
|
|
if (shnum == SHN_UNDEF)
|
|
shnum = shdr_size(shdr_start);
|
|
|
|
for (int i = 0; i < shnum; i++) {
|
|
Elf_Shdr *shdr = get_index(shdr_start, shentsize, i);
|
|
void *end;
|
|
|
|
if (shdr_type(shdr) != SHT_RELA)
|
|
continue;
|
|
|
|
rel = (void *)ehdr + shdr_offset(shdr);
|
|
end = (void *)rel + shdr_size(shdr);
|
|
|
|
for (; (void *)rel < end; rel = (void *)rel + shdr_entsize(shdr)) {
|
|
uint64_t offset = rela_offset(rel);
|
|
|
|
if (offset >= start_loc && offset < start_loc + size) {
|
|
if (ptr + long_size > array_end) {
|
|
snprintf(m_err, ERRSTR_MAXSZ,
|
|
"Too many relocations");
|
|
return -1;
|
|
}
|
|
|
|
/* Make sure this has the correct type */
|
|
if (rela_info(rel) != rela_type) {
|
|
snprintf(m_err, ERRSTR_MAXSZ,
|
|
"rela has type %lx but expected %lx\n",
|
|
(long)rela_info(rel), rela_type);
|
|
return -1;
|
|
}
|
|
|
|
if (long_size == 4)
|
|
*(uint32_t *)ptr = rela_addend(rel);
|
|
else
|
|
*(uint64_t *)ptr = rela_addend(rel);
|
|
ptr += long_size;
|
|
count++;
|
|
}
|
|
}
|
|
}
|
|
return count;
|
|
}
|
|
|
|
/* Put the sorted vals back into the relocation elements */
|
|
static void replace_relocs(void *ptr, uint64_t size, Elf_Ehdr *ehdr, uint64_t start_loc)
|
|
{
|
|
Elf_Shdr *shdr_start;
|
|
Elf_Rela *rel;
|
|
unsigned int shnum;
|
|
int shentsize;
|
|
|
|
shdr_start = (Elf_Shdr *)((char *)ehdr + ehdr_shoff(ehdr));
|
|
shentsize = ehdr_shentsize(ehdr);
|
|
|
|
shnum = ehdr_shnum(ehdr);
|
|
if (shnum == SHN_UNDEF)
|
|
shnum = shdr_size(shdr_start);
|
|
|
|
for (int i = 0; i < shnum; i++) {
|
|
Elf_Shdr *shdr = get_index(shdr_start, shentsize, i);
|
|
void *end;
|
|
|
|
if (shdr_type(shdr) != SHT_RELA)
|
|
continue;
|
|
|
|
rel = (void *)ehdr + shdr_offset(shdr);
|
|
end = (void *)rel + shdr_size(shdr);
|
|
|
|
for (; (void *)rel < end; rel = (void *)rel + shdr_entsize(shdr)) {
|
|
uint64_t offset = rela_offset(rel);
|
|
|
|
if (offset >= start_loc && offset < start_loc + size) {
|
|
if (long_size == 4)
|
|
rela_write_addend(rel, *(uint32_t *)ptr);
|
|
else
|
|
rela_write_addend(rel, *(uint64_t *)ptr);
|
|
ptr += long_size;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static int fill_addrs(void *ptr, uint64_t size, void *addrs)
|
|
{
|
|
void *end = ptr + size;
|
|
int count = 0;
|
|
|
|
for (; ptr < end; ptr += long_size, addrs += long_size, count++) {
|
|
if (long_size == 4)
|
|
*(uint32_t *)ptr = elf_parser.r(addrs);
|
|
else
|
|
*(uint64_t *)ptr = elf_parser.r8(addrs);
|
|
}
|
|
return count;
|
|
}
|
|
|
|
static void replace_addrs(void *ptr, uint64_t size, void *addrs)
|
|
{
|
|
void *end = ptr + size;
|
|
|
|
for (; ptr < end; ptr += long_size, addrs += long_size) {
|
|
if (long_size == 4)
|
|
elf_parser.w(*(uint32_t *)ptr, addrs);
|
|
else
|
|
elf_parser.w8(*(uint64_t *)ptr, addrs);
|
|
}
|
|
}
|
|
|
|
/* Sort the addresses stored between __start_mcount_loc to __stop_mcount_loc in vmlinux */
|
|
static void *sort_mcount_loc(void *arg)
|
|
{
|
|
struct elf_mcount_loc *emloc = (struct elf_mcount_loc *)arg;
|
|
uint64_t offset = emloc->start_mcount_loc - shdr_addr(emloc->init_data_sec)
|
|
+ shdr_offset(emloc->init_data_sec);
|
|
uint64_t size = emloc->stop_mcount_loc - emloc->start_mcount_loc;
|
|
unsigned char *start_loc = (void *)emloc->ehdr + offset;
|
|
Elf_Ehdr *ehdr = emloc->ehdr;
|
|
void *e_msg = NULL;
|
|
void *vals;
|
|
int count;
|
|
|
|
vals = malloc(long_size * size);
|
|
if (!vals) {
|
|
snprintf(m_err, ERRSTR_MAXSZ, "Failed to allocate sort array");
|
|
pthread_exit(m_err);
|
|
}
|
|
|
|
if (sort_reloc) {
|
|
count = fill_relocs(vals, size, ehdr, emloc->start_mcount_loc);
|
|
/* gcc may use relocs to save the addresses, but clang does not. */
|
|
if (!count) {
|
|
count = fill_addrs(vals, size, start_loc);
|
|
sort_reloc = 0;
|
|
}
|
|
} else
|
|
count = fill_addrs(vals, size, start_loc);
|
|
|
|
if (count < 0) {
|
|
e_msg = m_err;
|
|
goto out;
|
|
}
|
|
|
|
if (count != size / long_size) {
|
|
snprintf(m_err, ERRSTR_MAXSZ, "Expected %u mcount elements but found %u\n",
|
|
(int)(size / long_size), count);
|
|
e_msg = m_err;
|
|
goto out;
|
|
}
|
|
|
|
/* zero out any locations not found by function list */
|
|
if (function_list_size) {
|
|
for (void *ptr = vals; ptr < vals + size; ptr += long_size) {
|
|
uint64_t key;
|
|
|
|
key = long_size == 4 ? *(uint32_t *)ptr : *(uint64_t *)ptr;
|
|
if (!find_func(key)) {
|
|
if (long_size == 4)
|
|
*(uint32_t *)ptr = 0;
|
|
else
|
|
*(uint64_t *)ptr = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
compare_values = long_size == 4 ? compare_values_32 : compare_values_64;
|
|
|
|
qsort(vals, count, long_size, compare_values);
|
|
|
|
if (sort_reloc)
|
|
replace_relocs(vals, size, ehdr, emloc->start_mcount_loc);
|
|
else
|
|
replace_addrs(vals, size, start_loc);
|
|
|
|
out:
|
|
free(vals);
|
|
|
|
pthread_exit(e_msg);
|
|
}
|
|
|
|
/* Get the address of __start_mcount_loc and __stop_mcount_loc in System.map */
|
|
static void get_mcount_loc(struct elf_mcount_loc *emloc, Elf_Shdr *symtab_sec,
|
|
const char *strtab)
|
|
{
|
|
Elf_Sym *sym, *end_sym;
|
|
int symentsize = shdr_entsize(symtab_sec);
|
|
int found = 0;
|
|
|
|
sym = (void *)emloc->ehdr + shdr_offset(symtab_sec);
|
|
end_sym = (void *)sym + shdr_size(symtab_sec);
|
|
|
|
while (sym < end_sym) {
|
|
if (!strcmp(strtab + sym_name(sym), "__start_mcount_loc")) {
|
|
emloc->start_mcount_loc = sym_value(sym);
|
|
if (++found == 2)
|
|
break;
|
|
} else if (!strcmp(strtab + sym_name(sym), "__stop_mcount_loc")) {
|
|
emloc->stop_mcount_loc = sym_value(sym);
|
|
if (++found == 2)
|
|
break;
|
|
}
|
|
sym = (void *)sym + symentsize;
|
|
}
|
|
|
|
if (!emloc->start_mcount_loc) {
|
|
fprintf(stderr, "get start_mcount_loc error!");
|
|
return;
|
|
}
|
|
|
|
if (!emloc->stop_mcount_loc) {
|
|
fprintf(stderr, "get stop_mcount_loc error!");
|
|
return;
|
|
}
|
|
}
|
|
#else /* MCOUNT_SORT_ENABLED */
|
|
static inline int parse_symbols(const char *fname) { return 0; }
|
|
#endif
|
|
|
|
static int do_sort(Elf_Ehdr *ehdr,
|
|
char const *const fname,
|
|
table_sort_t custom_sort)
|
|
{
|
|
int rc = -1;
|
|
Elf_Shdr *shdr_start;
|
|
Elf_Shdr *strtab_sec = NULL;
|
|
Elf_Shdr *symtab_sec = NULL;
|
|
Elf_Shdr *extab_sec = NULL;
|
|
Elf_Shdr *string_sec;
|
|
Elf_Sym *sym;
|
|
const Elf_Sym *symtab;
|
|
Elf32_Word *symtab_shndx = NULL;
|
|
Elf_Sym *sort_needed_sym = NULL;
|
|
Elf_Shdr *sort_needed_sec;
|
|
uint32_t *sort_needed_loc;
|
|
void *sym_start;
|
|
void *sym_end;
|
|
const char *secstrings;
|
|
const char *strtab;
|
|
char *extab_image;
|
|
int sort_need_index;
|
|
int symentsize;
|
|
int shentsize;
|
|
int idx;
|
|
int i;
|
|
unsigned int shnum;
|
|
unsigned int shstrndx;
|
|
#ifdef MCOUNT_SORT_ENABLED
|
|
struct elf_mcount_loc mstruct = {0};
|
|
#endif
|
|
#ifdef UNWINDER_ORC_ENABLED
|
|
unsigned int orc_ip_size = 0;
|
|
unsigned int orc_size = 0;
|
|
unsigned int orc_num_entries = 0;
|
|
#endif
|
|
|
|
shdr_start = (Elf_Shdr *)((char *)ehdr + ehdr_shoff(ehdr));
|
|
shentsize = ehdr_shentsize(ehdr);
|
|
|
|
shstrndx = ehdr_shstrndx(ehdr);
|
|
if (shstrndx == SHN_XINDEX)
|
|
shstrndx = shdr_link(shdr_start);
|
|
string_sec = get_index(shdr_start, shentsize, shstrndx);
|
|
secstrings = (const char *)ehdr + shdr_offset(string_sec);
|
|
|
|
shnum = ehdr_shnum(ehdr);
|
|
if (shnum == SHN_UNDEF)
|
|
shnum = shdr_size(shdr_start);
|
|
|
|
for (i = 0; i < shnum; i++) {
|
|
Elf_Shdr *shdr = get_index(shdr_start, shentsize, i);
|
|
|
|
idx = shdr_name(shdr);
|
|
if (!strcmp(secstrings + idx, "__ex_table"))
|
|
extab_sec = shdr;
|
|
if (!strcmp(secstrings + idx, ".symtab"))
|
|
symtab_sec = shdr;
|
|
if (!strcmp(secstrings + idx, ".strtab"))
|
|
strtab_sec = shdr;
|
|
|
|
if (shdr_type(shdr) == SHT_SYMTAB_SHNDX)
|
|
symtab_shndx = (Elf32_Word *)((const char *)ehdr +
|
|
shdr_offset(shdr));
|
|
|
|
#ifdef MCOUNT_SORT_ENABLED
|
|
/* locate the .init.data section in vmlinux */
|
|
if (!strcmp(secstrings + idx, ".init.data"))
|
|
mstruct.init_data_sec = shdr;
|
|
#endif
|
|
|
|
#ifdef UNWINDER_ORC_ENABLED
|
|
/* locate the ORC unwind tables */
|
|
if (!strcmp(secstrings + idx, ".orc_unwind_ip")) {
|
|
orc_ip_size = shdr_size(shdr);
|
|
g_orc_ip_table = (int *)((void *)ehdr +
|
|
shdr_offset(shdr));
|
|
}
|
|
if (!strcmp(secstrings + idx, ".orc_unwind")) {
|
|
orc_size = shdr_size(shdr);
|
|
g_orc_table = (struct orc_entry *)((void *)ehdr +
|
|
shdr_offset(shdr));
|
|
}
|
|
#endif
|
|
} /* for loop */
|
|
|
|
#ifdef UNWINDER_ORC_ENABLED
|
|
if (!g_orc_ip_table || !g_orc_table) {
|
|
fprintf(stderr,
|
|
"incomplete ORC unwind tables in file: %s\n", fname);
|
|
goto out;
|
|
}
|
|
|
|
orc_num_entries = orc_ip_size / sizeof(int);
|
|
if (orc_ip_size % sizeof(int) != 0 ||
|
|
orc_size % sizeof(struct orc_entry) != 0 ||
|
|
orc_num_entries != orc_size / sizeof(struct orc_entry)) {
|
|
fprintf(stderr,
|
|
"inconsistent ORC unwind table entries in file: %s\n",
|
|
fname);
|
|
goto out;
|
|
}
|
|
|
|
/* create thread to sort ORC unwind tables concurrently */
|
|
if (pthread_create(&orc_sort_thread, NULL,
|
|
sort_orctable, &orc_ip_size)) {
|
|
fprintf(stderr,
|
|
"pthread_create orc_sort_thread failed '%s': %s\n",
|
|
strerror(errno), fname);
|
|
goto out;
|
|
}
|
|
#endif
|
|
if (!extab_sec) {
|
|
fprintf(stderr, "no __ex_table in file: %s\n", fname);
|
|
goto out;
|
|
}
|
|
|
|
if (!symtab_sec) {
|
|
fprintf(stderr, "no .symtab in file: %s\n", fname);
|
|
goto out;
|
|
}
|
|
|
|
if (!strtab_sec) {
|
|
fprintf(stderr, "no .strtab in file: %s\n", fname);
|
|
goto out;
|
|
}
|
|
|
|
extab_image = (void *)ehdr + shdr_offset(extab_sec);
|
|
strtab = (const char *)ehdr + shdr_offset(strtab_sec);
|
|
symtab = (const Elf_Sym *)((const char *)ehdr + shdr_offset(symtab_sec));
|
|
|
|
#ifdef MCOUNT_SORT_ENABLED
|
|
mstruct.ehdr = ehdr;
|
|
get_mcount_loc(&mstruct, symtab_sec, strtab);
|
|
|
|
if (!mstruct.init_data_sec || !mstruct.start_mcount_loc || !mstruct.stop_mcount_loc) {
|
|
fprintf(stderr,
|
|
"incomplete mcount's sort in file: %s\n",
|
|
fname);
|
|
goto out;
|
|
}
|
|
|
|
/* create thread to sort mcount_loc concurrently */
|
|
if (pthread_create(&mcount_sort_thread, NULL, &sort_mcount_loc, &mstruct)) {
|
|
fprintf(stderr,
|
|
"pthread_create mcount_sort_thread failed '%s': %s\n",
|
|
strerror(errno), fname);
|
|
goto out;
|
|
}
|
|
#endif
|
|
|
|
if (custom_sort) {
|
|
custom_sort(extab_image, shdr_size(extab_sec));
|
|
} else {
|
|
int num_entries = shdr_size(extab_sec) / extable_ent_size;
|
|
qsort(extab_image, num_entries,
|
|
extable_ent_size, compare_extable);
|
|
}
|
|
|
|
/* find the flag main_extable_sort_needed */
|
|
sym_start = (void *)ehdr + shdr_offset(symtab_sec);
|
|
sym_end = sym_start + shdr_size(symtab_sec);
|
|
symentsize = shdr_entsize(symtab_sec);
|
|
|
|
for (sym = sym_start; (void *)sym + symentsize < sym_end;
|
|
sym = (void *)sym + symentsize) {
|
|
if (sym_type(sym) != STT_OBJECT)
|
|
continue;
|
|
if (!strcmp(strtab + sym_name(sym),
|
|
"main_extable_sort_needed")) {
|
|
sort_needed_sym = sym;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!sort_needed_sym) {
|
|
fprintf(stderr,
|
|
"no main_extable_sort_needed symbol in file: %s\n",
|
|
fname);
|
|
goto out;
|
|
}
|
|
|
|
sort_need_index = get_secindex(sym_shndx(sym),
|
|
((void *)sort_needed_sym - (void *)symtab) / symentsize,
|
|
symtab_shndx);
|
|
sort_needed_sec = get_index(shdr_start, shentsize, sort_need_index);
|
|
sort_needed_loc = (void *)ehdr +
|
|
shdr_offset(sort_needed_sec) +
|
|
sym_value(sort_needed_sym) - shdr_addr(sort_needed_sec);
|
|
|
|
/* extable has been sorted, clear the flag */
|
|
elf_parser.w(0, sort_needed_loc);
|
|
rc = 0;
|
|
|
|
out:
|
|
#ifdef UNWINDER_ORC_ENABLED
|
|
if (orc_sort_thread) {
|
|
void *retval = NULL;
|
|
/* wait for ORC tables sort done */
|
|
rc = pthread_join(orc_sort_thread, &retval);
|
|
if (rc) {
|
|
fprintf(stderr,
|
|
"pthread_join failed '%s': %s\n",
|
|
strerror(errno), fname);
|
|
} else if (retval) {
|
|
rc = -1;
|
|
fprintf(stderr,
|
|
"failed to sort ORC tables '%s': %s\n",
|
|
(char *)retval, fname);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#ifdef MCOUNT_SORT_ENABLED
|
|
if (mcount_sort_thread) {
|
|
void *retval = NULL;
|
|
/* wait for mcount sort done */
|
|
rc = pthread_join(mcount_sort_thread, &retval);
|
|
if (rc) {
|
|
fprintf(stderr,
|
|
"pthread_join failed '%s': %s\n",
|
|
strerror(errno), fname);
|
|
} else if (retval) {
|
|
rc = -1;
|
|
fprintf(stderr,
|
|
"failed to sort mcount '%s': %s\n",
|
|
(char *)retval, fname);
|
|
}
|
|
}
|
|
#endif
|
|
return rc;
|
|
}
|
|
|
|
static int compare_relative_table(const void *a, const void *b)
|
|
{
|
|
int32_t av = (int32_t)elf_parser.r(a);
|
|
int32_t bv = (int32_t)elf_parser.r(b);
|
|
|
|
if (av < bv)
|
|
return -1;
|
|
if (av > bv)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
static void sort_relative_table(char *extab_image, int image_size)
|
|
{
|
|
int i = 0;
|
|
|
|
/*
|
|
* Do the same thing the runtime sort does, first normalize to
|
|
* being relative to the start of the section.
|
|
*/
|
|
while (i < image_size) {
|
|
uint32_t *loc = (uint32_t *)(extab_image + i);
|
|
elf_parser.w(elf_parser.r(loc) + i, loc);
|
|
i += 4;
|
|
}
|
|
|
|
qsort(extab_image, image_size / 8, 8, compare_relative_table);
|
|
|
|
/* Now denormalize. */
|
|
i = 0;
|
|
while (i < image_size) {
|
|
uint32_t *loc = (uint32_t *)(extab_image + i);
|
|
elf_parser.w(elf_parser.r(loc) - i, loc);
|
|
i += 4;
|
|
}
|
|
}
|
|
|
|
static void sort_relative_table_with_data(char *extab_image, int image_size)
|
|
{
|
|
int i = 0;
|
|
|
|
while (i < image_size) {
|
|
uint32_t *loc = (uint32_t *)(extab_image + i);
|
|
|
|
elf_parser.w(elf_parser.r(loc) + i, loc);
|
|
elf_parser.w(elf_parser.r(loc + 1) + i + 4, loc + 1);
|
|
/* Don't touch the fixup type or data */
|
|
|
|
i += sizeof(uint32_t) * 3;
|
|
}
|
|
|
|
qsort(extab_image, image_size / 12, 12, compare_relative_table);
|
|
|
|
i = 0;
|
|
while (i < image_size) {
|
|
uint32_t *loc = (uint32_t *)(extab_image + i);
|
|
|
|
elf_parser.w(elf_parser.r(loc) - i, loc);
|
|
elf_parser.w(elf_parser.r(loc + 1) - (i + 4), loc + 1);
|
|
/* Don't touch the fixup type or data */
|
|
|
|
i += sizeof(uint32_t) * 3;
|
|
}
|
|
}
|
|
|
|
static int do_file(char const *const fname, void *addr)
|
|
{
|
|
Elf_Ehdr *ehdr = addr;
|
|
table_sort_t custom_sort = NULL;
|
|
|
|
switch (elf_map_machine(ehdr)) {
|
|
#ifdef MCOUNT_SORT_ENABLED
|
|
case EM_AARCH64:
|
|
/* arm64 also needs RELA-based weak-function fixups. */
|
|
sort_reloc = true;
|
|
rela_type = 0x403;
|
|
/* fallthrough */
|
|
case EM_RISCV:
|
|
/* arm64 and RISC-V place patchable entries before the function. */
|
|
before_func = 8;
|
|
#else
|
|
case EM_AARCH64:
|
|
case EM_RISCV:
|
|
#endif
|
|
/* fallthrough */
|
|
case EM_386:
|
|
case EM_LOONGARCH:
|
|
case EM_S390:
|
|
case EM_X86_64:
|
|
custom_sort = sort_relative_table_with_data;
|
|
break;
|
|
case EM_PARISC:
|
|
case EM_PPC:
|
|
case EM_PPC64:
|
|
custom_sort = sort_relative_table;
|
|
break;
|
|
case EM_ARCOMPACT:
|
|
case EM_ARCV2:
|
|
case EM_ARM:
|
|
case EM_MICROBLAZE:
|
|
case EM_MIPS:
|
|
case EM_XTENSA:
|
|
break;
|
|
default:
|
|
fprintf(stderr, "unrecognized e_machine %d %s\n",
|
|
elf_parser.r2(&ehdr->e32.e_machine), fname);
|
|
return -1;
|
|
}
|
|
|
|
switch (elf_map_long_size(addr)) {
|
|
case 4:
|
|
compare_extable = compare_extable_32,
|
|
long_size = 4;
|
|
extable_ent_size = 8;
|
|
|
|
if (elf_parser.r2(&ehdr->e32.e_ehsize) != sizeof(Elf32_Ehdr) ||
|
|
elf_parser.r2(&ehdr->e32.e_shentsize) != sizeof(Elf32_Shdr)) {
|
|
fprintf(stderr,
|
|
"unrecognized ET_EXEC/ET_DYN file: %s\n", fname);
|
|
return -1;
|
|
}
|
|
|
|
break;
|
|
case 8:
|
|
compare_extable = compare_extable_64,
|
|
long_size = 8;
|
|
extable_ent_size = 16;
|
|
|
|
if (elf_parser.r2(&ehdr->e64.e_ehsize) != sizeof(Elf64_Ehdr) ||
|
|
elf_parser.r2(&ehdr->e64.e_shentsize) != sizeof(Elf64_Shdr)) {
|
|
fprintf(stderr,
|
|
"unrecognized ET_EXEC/ET_DYN file: %s\n",
|
|
fname);
|
|
return -1;
|
|
}
|
|
|
|
break;
|
|
default:
|
|
fprintf(stderr, "unrecognized ELF class %d %s\n",
|
|
ehdr->e32.e_ident[EI_CLASS], fname);
|
|
return -1;
|
|
}
|
|
|
|
return do_sort(ehdr, fname, custom_sort);
|
|
}
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
int i, n_error = 0; /* gcc-4.3.0 false positive complaint */
|
|
size_t size = 0;
|
|
void *addr = NULL;
|
|
int c;
|
|
|
|
while ((c = getopt(argc, argv, "s:")) >= 0) {
|
|
switch (c) {
|
|
case 's':
|
|
if (parse_symbols(optarg) < 0) {
|
|
fprintf(stderr, "Could not parse %s\n", optarg);
|
|
return -1;
|
|
}
|
|
break;
|
|
default:
|
|
fprintf(stderr, "usage: sorttable [-s nm-file] vmlinux...\n");
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if ((argc - optind) < 1) {
|
|
fprintf(stderr, "usage: sorttable vmlinux...\n");
|
|
return 0;
|
|
}
|
|
|
|
/* Process each file in turn, allowing deep failure. */
|
|
for (i = optind; i < argc; i++) {
|
|
addr = elf_map(argv[i], &size, (1 << ET_EXEC) | (1 << ET_DYN));
|
|
if (!addr) {
|
|
++n_error;
|
|
continue;
|
|
}
|
|
|
|
if (do_file(argv[i], addr))
|
|
++n_error;
|
|
|
|
elf_unmap(addr, size);
|
|
}
|
|
|
|
return !!n_error;
|
|
}
|