* elf-compare.c
*
* Copyright (C) 2014 Seth Jennings <sjenning@redhat.com>
* Copyright (C) 2013-2014 Josh Poimboeuf <jpoimboe@redhat.com>
* Copyright (C) 2022 Longjun Luo <luolongjun@huawei.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA,
* 02110-1301, USA.
*/
#include <libgen.h>
#include "log.h"
#include "elf-common.h"
#include "elf-compare.h"
#include "elf-insn.h"
static int compare_correlated_section(struct section *sec, struct section *twin);
static void compare_correlated_symbol(struct symbol *sym, struct symbol *twin)
{
if (sym->type != twin->type) {
ERROR("Symbol '%s' type mismatched", sym->name);
}
if (sym->sym.st_info != twin->sym.st_info) {
ERROR("Symbol '%s' st_info mismatched", sym->name);
}
* For local symbols, we handle them based on their matching sections.
*/
if ((sym->sym.st_shndx == SHN_UNDEF) ||
(sym->sym.st_shndx == SHN_ABS) ||
(sym->sym.st_shndx == SHN_COMMON)) {
sym->status = SAME;
return;
}
if ((sym->sec == NULL) || (sym->sym.st_shndx == SHN_ABS)) {
ERROR("Symbol '%s' don't have section\n", sym->name);
}
if (sym->sec->twin != twin->sec) {
ERROR("Symbol '%s' section mismatched", sym->name);
}
compare_correlated_section(sym->sec, twin->sec);
if (sym->sec->status == CHANGED) {
sym->status = CHANGED;
} else if (!is_rela_section(sym->sec) &&
(sym->sec->rela != NULL) &&
(sym->sec->rela->status == CHANGED)) {
sym->status = CHANGED;
} else {
sym->status = SAME;
}
}
void upatch_compare_symbols(struct upatch_elf *uelf)
{
struct symbol *sym;
list_for_each_entry(sym, &uelf->symbols, list) {
if (is_symbol_ignored(sym)) {
continue;
}
if (is_special_static_symbol(sym)) {
sym->status = SAME;
} else if (sym->twin == NULL) {
sym->status = NEW;
} else {
compare_correlated_symbol(sym, sym->twin);
}
}
}
static bool rela_equal(struct rela *rela1, struct rela *rela2)
{
if (rela1->type != rela2->type || rela1->offset != rela2->offset) {
return false;
}
if (rela1->string) {
return rela2->string && !strcmp(rela1->string, rela2->string);
}
if (rela1->addend != rela2->addend) {
log_debug("relocation addend has changed from %ld to %ld",
rela1->addend, rela2->addend);
return false;
}
return !mangled_strcmp(rela1->sym->name, rela2->sym->name);
}
static void compare_correlated_rela_section(struct section *relasec,
struct section *relasec_twin)
{
struct rela *rela1 = NULL;
struct rela *rela2 = NULL;
rela2 = list_entry(relasec_twin->relas.next, struct rela, list);
list_for_each_entry(rela1, &relasec->relas, list) {
if (rela_equal(rela1, rela2)) {
rela2 = list_entry(rela2->list.next, struct rela, list);
continue;
}
relasec->status = CHANGED;
return;
}
relasec->status = SAME;
}
static void compare_correlated_nonrela_section(struct section *sec,
struct section *sectwin)
{
if (sec->sh.sh_type != SHT_NOBITS &&
(sec->data->d_size != sectwin->data->d_size ||
memcmp(sec->data->d_buf, sectwin->data->d_buf, sec->data->d_size))) {
sec->status = CHANGED;
} else {
sec->status = SAME;
}
}
static int compare_correlated_section(struct section *sec, struct section *twin)
{
When we change the initial value of variables
sh_flags & sh_addralign may change in .rodata section */
if ((sec->sh.sh_type != twin->sh.sh_type) ||
(sec->sh.sh_entsize != twin->sh.sh_entsize)) {
ERROR("%s section header details differ from %s",
sec->name, twin->name);
return -1;
}
if (sec->sh.sh_flags != twin->sh.sh_flags) {
log_warn("Section '%s' sh_flags changed from %ld to %ld\n",
sec->name, sec->sh.sh_flags, twin->sh.sh_flags);
}
if (sec->sh.sh_addralign != twin->sh.sh_addralign) {
log_warn("Section '%s' sh_addralign changed from %ld to %ld\n",
sec->name, sec->sh.sh_addralign, twin->sh.sh_addralign);
}
if (is_note_section(sec)) {
sec->status = SAME;
return 0;
}
if (!strcmp(sec->name, ".rela__patchable_function_entries") ||
!strcmp(sec->name, "__patchable_function_entries")) {
sec->status = SAME;
return 0;
}
if (sec->sh.sh_size != twin->sh.sh_size ||
sec->data->d_size != twin->data->d_size ||
(sec->rela && !twin->rela) || (!sec->rela && twin->rela)) {
sec->status = CHANGED;
return 0;
}
if (is_rela_section(sec)) {
compare_correlated_rela_section(sec, twin);
} else {
compare_correlated_nonrela_section(sec, twin);
}
return 0;
}
static void update_section_status(struct section *sec, enum status status)
{
if (sec == NULL) {
return;
}
if (sec->twin != NULL) {
sec->twin->status = status;
}
if (is_rela_section(sec)) {
if ((sec->base != NULL) && (sec->base->bundle_sym != NULL) && status != SAME) {
sec->base->bundle_sym->status = status;
}
} else {
if (sec->bundle_sym != NULL) {
sec->bundle_sym->status = status;
}
}
}
void upatch_compare_sections(struct upatch_elf *uelf)
{
struct section *sec = NULL;
list_for_each_entry(sec, &uelf->sections, list) {
if (sec->ignored) {
continue;
}
if (is_special_static_section(sec)) {
sec->status = SAME;
} else if (sec->twin == NULL) {
sec->status = NEW;
} else {
compare_correlated_section(sec, sec->twin);
}
update_section_status(sec, sec->status);
update_section_status(sec->twin, sec->status);
}
}