NetBpfLoad: introduce ElfObject struct (class)

(prep work for improving loader performance)

Test: TreeHugger
Flag: EXEMPT mainline
Signed-off-by: Maciej Żenczykowski <maze@google.com>
Change-Id: If62b1de1020b89a28e06cad45153281e2ffcd6e0
This commit is contained in:
Maciej Żenczykowski 2025-08-01 12:43:01 -07:00
commit 6e910b728d

View file

@ -170,235 +170,244 @@ typedef struct {
unique_fd prog_fd; // fd after loading
} codeSection;
static int readElfHeader(ifstream& elfFile, Elf64_Ehdr* eh) {
elfFile.seekg(0);
if (elfFile.fail()) return -1;
struct ElfObject {
const char * path;
ifstream file;
if (!elfFile.read((char*)eh, sizeof(*eh))) return -1;
return 0;
}
// Reads all section header tables into an Shdr array
static int readSectionHeadersAll(ifstream& elfFile, vector<Elf64_Shdr>& shTable) {
Elf64_Ehdr eh;
int ret = 0;
ret = readElfHeader(elfFile, &eh);
if (ret) return ret;
elfFile.seekg(eh.e_shoff);
if (elfFile.fail()) return -1;
// Read shdr table entries
shTable.resize(eh.e_shnum);
if (!elfFile.read((char*)shTable.data(), (eh.e_shnum * eh.e_shentsize))) return -ENOMEM;
return 0;
}
// Read a section by its index - for ex to get sec hdr strtab blob
static int readSectionByIdx(ifstream& elfFile, int id, vector<char>& sec) {
vector<Elf64_Shdr> shTable;
int ret = readSectionHeadersAll(elfFile, shTable);
if (ret) return ret;
elfFile.seekg(shTable[id].sh_offset);
if (elfFile.fail()) return -1;
sec.resize(shTable[id].sh_size);
if (!elfFile.read(sec.data(), shTable[id].sh_size)) return -1;
return 0;
}
// Read whole section header string table
static int readSectionHeaderStrtab(ifstream& elfFile, vector<char>& strtab) {
Elf64_Ehdr eh;
int ret = readElfHeader(elfFile, &eh);
if (ret) return ret;
ret = readSectionByIdx(elfFile, eh.e_shstrndx, strtab);
if (ret) return ret;
return 0;
}
// Get name from offset in strtab
static int getSymName(ifstream& elfFile, int nameOff, string& name) {
int ret;
vector<char> secStrTab;
ret = readSectionHeaderStrtab(elfFile, secStrTab);
if (ret) return ret;
if (nameOff >= (int)secStrTab.size()) return -1;
name = string((char*)secStrTab.data() + nameOff);
return 0;
}
// Reads a full section by name - example to get the GPL license
template <typename T>
static int readSectionByName(const char* name, ifstream& elfFile, vector<T>& data) {
vector<char> secStrTab;
vector<Elf64_Shdr> shTable;
int ret;
ret = readSectionHeadersAll(elfFile, shTable);
if (ret) return ret;
ret = readSectionHeaderStrtab(elfFile, secStrTab);
if (ret) return ret;
for (int i = 0; i < (int)shTable.size(); i++) {
char* secname = secStrTab.data() + shTable[i].sh_name;
if (!secname) continue;
if (!strcmp(secname, name)) {
elfFile.seekg(shTable[i].sh_offset);
if (elfFile.fail()) return -1;
if (shTable[i].sh_size % sizeof(T)) return -1;
data.resize(shTable[i].sh_size / sizeof(T));
if (!elfFile.read(reinterpret_cast<char*>(data.data()), shTable[i].sh_size))
return -1;
return 0;
}
ElfObject(const char* elfPath) : path(elfPath), file(path, ios::in | ios::binary) {
if (!file.is_open()) abort();
}
return -2;
}
static int readSectionByType(ifstream& elfFile, int type, vector<char>& data) {
int ret;
vector<Elf64_Shdr> shTable;
int readElfHeader(Elf64_Ehdr* eh) {
file.seekg(0);
if (file.fail()) return -1;
ret = readSectionHeadersAll(elfFile, shTable);
if (ret) return ret;
for (int i = 0; i < (int)shTable.size(); i++) {
if ((int)shTable[i].sh_type != type) continue;
elfFile.seekg(shTable[i].sh_offset);
if (elfFile.fail()) return -1;
data.resize(shTable[i].sh_size);
if (!elfFile.read(data.data(), shTable[i].sh_size)) return -1;
if (!file.read((char*)eh, sizeof(*eh))) return -1;
return 0;
}
return -2;
}
static bool symCompare(Elf64_Sym a, Elf64_Sym b) {
return (a.st_value < b.st_value);
}
// Reads all section header tables into an Shdr array
int readSectionHeadersAll(vector<Elf64_Shdr>& shTable) {
Elf64_Ehdr eh;
int ret = 0;
static int readSymTab(ifstream& elfFile, int sort, vector<Elf64_Sym>& data) {
int ret, numElems;
Elf64_Sym* buf;
vector<char> secData;
ret = readSectionByType(elfFile, SHT_SYMTAB, secData);
if (ret) return ret;
buf = (Elf64_Sym*)secData.data();
numElems = (secData.size() / sizeof(Elf64_Sym));
data.assign(buf, buf + numElems);
if (sort) std::sort(data.begin(), data.end(), symCompare);
return 0;
}
static int getSectionSymNames(ifstream& elfFile, const string& sectionName, vector<string>& names,
optional<unsigned> symbolType = std::nullopt) {
int ret;
string name;
vector<Elf64_Sym> symtab;
vector<Elf64_Shdr> shTable;
ret = readSymTab(elfFile, 1 /* sort */, symtab);
if (ret) return ret;
// Get index of section
ret = readSectionHeadersAll(elfFile, shTable);
if (ret) return ret;
int sec_idx = -1;
for (int i = 0; i < (int)shTable.size(); i++) {
ret = getSymName(elfFile, shTable[i].sh_name, name);
ret = readElfHeader(&eh);
if (ret) return ret;
if (!name.compare(sectionName)) {
sec_idx = i;
break;
file.seekg(eh.e_shoff);
if (file.fail()) return -1;
// Read shdr table entries
shTable.resize(eh.e_shnum);
if (!file.read((char*)shTable.data(), (eh.e_shnum * eh.e_shentsize))) return -ENOMEM;
return 0;
}
// Read a section by its index - for ex to get sec hdr strtab blob
int readSectionByIdx(int id, vector<char>& sec) {
vector<Elf64_Shdr> shTable;
int ret = readSectionHeadersAll(shTable);
if (ret) return ret;
file.seekg(shTable[id].sh_offset);
if (file.fail()) return -1;
sec.resize(shTable[id].sh_size);
if (!file.read(sec.data(), shTable[id].sh_size)) return -1;
return 0;
}
// Read whole section header string table
int readSectionHeaderStrtab(vector<char>& strtab) {
Elf64_Ehdr eh;
int ret = readElfHeader(&eh);
if (ret) return ret;
ret = readSectionByIdx(eh.e_shstrndx, strtab);
if (ret) return ret;
return 0;
}
// Get name from offset in strtab
int getSymName(int nameOff, string& name) {
int ret;
vector<char> secStrTab;
ret = readSectionHeaderStrtab(secStrTab);
if (ret) return ret;
if (nameOff >= (int)secStrTab.size()) return -1;
name = string((char*)secStrTab.data() + nameOff);
return 0;
}
// Reads a full section by name - example to get the GPL license
template <typename T>
int readSectionByName(const char* name, vector<T>& data) {
vector<char> secStrTab;
vector<Elf64_Shdr> shTable;
int ret;
ret = readSectionHeadersAll(shTable);
if (ret) return ret;
ret = readSectionHeaderStrtab(secStrTab);
if (ret) return ret;
for (int i = 0; i < (int)shTable.size(); i++) {
char* secname = secStrTab.data() + shTable[i].sh_name;
if (!secname) continue;
if (!strcmp(secname, name)) {
file.seekg(shTable[i].sh_offset);
if (file.fail()) return -1;
if (shTable[i].sh_size % sizeof(T)) return -1;
data.resize(shTable[i].sh_size / sizeof(T));
if (!file.read(reinterpret_cast<char*>(data.data()), shTable[i].sh_size))
return -1;
return 0;
}
}
return -2;
}
// No section found with matching name
if (sec_idx == -1) {
ALOGW("No %s section could be found in elf object", sectionName.c_str());
return -1;
}
int readSectionByType(int type, vector<char>& data) {
int ret;
vector<Elf64_Shdr> shTable;
for (int i = 0; i < (int)symtab.size(); i++) {
if (symbolType.has_value() && ELF_ST_TYPE(symtab[i].st_info) != symbolType) continue;
ret = readSectionHeadersAll(shTable);
if (ret) return ret;
if (symtab[i].st_shndx == sec_idx) {
string s;
ret = getSymName(elfFile, symtab[i].st_name, s);
if (ret) return ret;
names.push_back(s);
}
}
for (int i = 0; i < (int)shTable.size(); i++) {
if ((int)shTable[i].sh_type != type) continue;
return 0;
}
file.seekg(shTable[i].sh_offset);
if (file.fail()) return -1;
static int getSymNameByIdx(ifstream& elfFile, int index, string& name) {
vector<Elf64_Sym> symtab;
int ret = 0;
data.resize(shTable[i].sh_size);
if (!file.read(data.data(), shTable[i].sh_size)) return -1;
ret = readSymTab(elfFile, 0 /* !sort */, symtab);
if (ret) return ret;
if (index >= (int)symtab.size()) return -1;
return getSymName(elfFile, symtab[index].st_name, name);
}
static int getSymOffsetByName(ifstream &elfFile, const char *name, int *off) {
vector<Elf64_Sym> symtab;
int ret = readSymTab(elfFile, 1 /* sort */, symtab);
if (ret) return ret;
for (int i = 0; i < (int)symtab.size(); i++) {
string s;
ret = getSymName(elfFile, symtab[i].st_name, s);
if (ret) continue;
if (!strcmp(s.c_str(), name)) {
*off = symtab[i].st_value;
return 0;
}
return -2;
}
return -1;
}
static bool symCompare(Elf64_Sym a, Elf64_Sym b) {
return (a.st_value < b.st_value);
}
int readSymTab(int sort, vector<Elf64_Sym>& data) {
int ret, numElems;
Elf64_Sym* buf;
vector<char> secData;
ret = readSectionByType(SHT_SYMTAB, secData);
if (ret) return ret;
buf = (Elf64_Sym*)secData.data();
numElems = (secData.size() / sizeof(Elf64_Sym));
data.assign(buf, buf + numElems);
if (sort) std::sort(data.begin(), data.end(), symCompare);
return 0;
}
int getSectionSymNames(const string& sectionName, vector<string>& names,
optional<unsigned> symbolType = std::nullopt) {
int ret;
string name;
vector<Elf64_Sym> symtab;
vector<Elf64_Shdr> shTable;
ret = readSymTab(1 /* sort */, symtab);
if (ret) return ret;
// Get index of section
ret = readSectionHeadersAll(shTable);
if (ret) return ret;
int sec_idx = -1;
for (int i = 0; i < (int)shTable.size(); i++) {
ret = getSymName(shTable[i].sh_name, name);
if (ret) return ret;
if (!name.compare(sectionName)) {
sec_idx = i;
break;
}
}
// No section found with matching name
if (sec_idx == -1) {
ALOGW("No %s section could be found in elf object", sectionName.c_str());
return -1;
}
for (int i = 0; i < (int)symtab.size(); i++) {
if (symbolType.has_value() && ELF_ST_TYPE(symtab[i].st_info) != symbolType) continue;
if (symtab[i].st_shndx == sec_idx) {
string s;
ret = getSymName(symtab[i].st_name, s);
if (ret) return ret;
names.push_back(s);
}
}
return 0;
}
int getSymNameByIdx(int index, string& name) {
vector<Elf64_Sym> symtab;
int ret = 0;
ret = readSymTab(0 /* !sort */, symtab);
if (ret) return ret;
if (index >= (int)symtab.size()) return -1;
return getSymName(symtab[index].st_name, name);
}
int getSymOffsetByName(const char *name, int *off) {
vector<Elf64_Sym> symtab;
int ret = readSymTab(1 /* sort */, symtab);
if (ret) return ret;
for (int i = 0; i < (int)symtab.size(); i++) {
string s;
ret = getSymName(symtab[i].st_name, s);
if (ret) continue;
if (!strcmp(s.c_str(), name)) {
*off = symtab[i].st_value;
return 0;
}
}
return -1;
}
};
// Read a section by its index - for ex to get sec hdr strtab blob
static int readCodeSections(ifstream& elfFile, vector<codeSection>& cs) {
int readCodeSections(ElfObject& elfObj, vector<codeSection>& cs) {
vector<Elf64_Shdr> shTable;
int entries, ret = 0;
ret = readSectionHeadersAll(elfFile, shTable);
ret = elfObj.readSectionHeadersAll(shTable);
if (ret) return ret;
entries = shTable.size();
vector<struct bpf_prog_def> pd;
ret = readSectionByName("progs", elfFile, pd);
ret = elfObj.readSectionByName("progs", pd);
if (ret) return ret;
vector<string> progDefNames;
ret = getSectionSymNames(elfFile, "progs", progDefNames);
ret = elfObj.getSectionSymNames("progs", progDefNames);
if (!pd.empty() && ret) return ret;
for (int i = 0; i < entries; i++) {
@ -406,7 +415,7 @@ static int readCodeSections(ifstream& elfFile, vector<codeSection>& cs) {
codeSection cs_temp;
cs_temp.type = BPF_PROG_TYPE_UNSPEC;
ret = getSymName(elfFile, shTable[i].sh_name, name);
ret = elfObj.getSymName(shTable[i].sh_name, name);
if (ret) return ret;
// This must be done before '/' is replaced with '_'.
@ -427,12 +436,12 @@ static int readCodeSections(ifstream& elfFile, vector<codeSection>& cs) {
cs_temp.name = name;
ret = readSectionByIdx(elfFile, i, cs_temp.data);
ret = elfObj.readSectionByIdx(i, cs_temp.data);
if (ret) return ret;
ALOGV("Loaded code section %d (%s)", i, name.c_str());
vector<string> csSymNames;
ret = getSectionSymNames(elfFile, oldName, csSymNames, STT_FUNC);
ret = elfObj.getSectionSymNames(oldName, csSymNames, STT_FUNC);
if (ret || !csSymNames.size()) return ret;
cs_temp.program_name = csSymNames[0];
for (size_t j = 0; j < progDefNames.size(); ++j) {
@ -444,11 +453,11 @@ static int readCodeSections(ifstream& elfFile, vector<codeSection>& cs) {
// Check for rel section
if (cs_temp.data.size() > 0 && i < entries) {
ret = getSymName(elfFile, shTable[i + 1].sh_name, name);
ret = elfObj.getSymName(shTable[i + 1].sh_name, name);
if (ret) return ret;
if (name == (".rel" + oldName)) {
ret = readSectionByIdx(elfFile, i + 1, cs_temp.rel_data);
ret = elfObj.readSectionByIdx(i + 1, cs_temp.rel_data);
if (ret) return ret;
ALOGV("Loaded relo section %d (%s)", i, name.c_str());
}
@ -462,7 +471,6 @@ static int readCodeSections(ifstream& elfFile, vector<codeSection>& cs) {
return 0;
}
static bool mapMatchesExpectations(const unique_fd& fd,
const struct bpf_map_def& mapDef, const enum bpf_map_type type) {
// bpfGetFd... family of functions require at minimum a 4.14 kernel,
@ -520,7 +528,7 @@ static bool mapMatchesExpectations(const unique_fd& fd,
return false;
}
static int setBtfDatasecSize(ifstream &elfFile, struct btf *btf,
static int setBtfDatasecSize(ElfObject &elfObj, struct btf *btf,
struct btf_type *bt) {
const char *name = btf__name_by_offset(btf, bt->name_off);
if (!name) {
@ -529,7 +537,7 @@ static int setBtfDatasecSize(ifstream &elfFile, struct btf *btf,
}
vector<char> data;
int ret = readSectionByName(name, elfFile, data);
int ret = elfObj.readSectionByName(name, data);
if (ret) {
ALOGE("Couldn't read section %s, ret: %d", name, ret);
return ret;
@ -538,7 +546,7 @@ static int setBtfDatasecSize(ifstream &elfFile, struct btf *btf,
return 0;
}
static int setBtfVarOffset(ifstream &elfFile, struct btf *btf,
static int setBtfVarOffset(ElfObject &elfObj, struct btf *btf,
struct btf_type *datasecBt) {
int i, vars = btf_vlen(datasecBt);
struct btf_var_secinfo *vsi;
@ -566,7 +574,7 @@ static int setBtfVarOffset(ifstream &elfFile, struct btf *btf,
}
int off;
int ret = getSymOffsetByName(elfFile, varName, &off);
int ret = elfObj.getSymOffsetByName(varName, &off);
if (ret) {
ALOGE("No offset found in symbol table, section: %s, var: %s, ret: %d",
datasecName, varName, ret);
@ -638,14 +646,14 @@ static int sanitizeBtf(struct btf *btf) {
return 0;
}
static int loadBtf(ifstream &elfFile, struct btf *btf) {
static int loadBtf(ElfObject &elfObj, struct btf *btf) {
int ret;
for (unsigned int i = 1; i < btf__type_cnt(btf); ++i) {
struct btf_type *bt = (struct btf_type *)btf__type_by_id(btf, i);
if (!btf_is_datasec(bt)) continue;
ret = setBtfDatasecSize(elfFile, btf, bt);
ret = setBtfDatasecSize(elfObj, btf, bt);
if (ret) return ret;
ret = setBtfVarOffset(elfFile, btf, bt);
ret = setBtfVarOffset(elfObj, btf, bt);
if (ret) return ret;
}
@ -830,7 +838,7 @@ static enum bpf_map_type sanitizeMapType(enum bpf_map_type type) {
return type;
}
static int createMaps(ifstream& elfFile, vector<struct bpf_map_def>& md, vector<unique_fd>& mapFds,
static int createMaps(ElfObject& elfObj, vector<struct bpf_map_def>& md, vector<unique_fd>& mapFds,
const unsigned int bpfloader_ver) {
int ret = 0;
vector<char> btfData;
@ -838,7 +846,7 @@ static int createMaps(ifstream& elfFile, vector<struct bpf_map_def>& md, vector<
auto btfGuard = base::make_scope_guard([&btf] { if (btf) btf__free(btf); });
if (isAtLeastKernelVersion(4, 19, 0)) {
// On Linux Kernels older than 4.18 BPF_BTF_LOAD command doesn't exist.
ret = readSectionByName(".BTF", elfFile, btfData);
ret = elfObj.readSectionByName(".BTF", btfData);
if (ret) {
ALOGE("Failed to read .BTF section, ret:%d", ret);
return ret;
@ -849,7 +857,7 @@ static int createMaps(ifstream& elfFile, vector<struct bpf_map_def>& md, vector<
return -errno;
}
ret = loadBtf(elfFile, btf);
ret = loadBtf(elfObj, btf);
if (ret) return ret;
}
@ -981,7 +989,7 @@ static void applyRelo(void* insnsPtr, Elf64_Addr offset, int fd) {
insn->src_reg = BPF_PSEUDO_MAP_FD;
}
static void applyMapRelo(ifstream& elfFile, const vector<struct bpf_map_def>& md,
static void applyMapRelo(ElfObject& elfObj, const vector<struct bpf_map_def>& md,
vector<unique_fd> &mapFds, vector<codeSection>& cs) {
for (unsigned k = 0; k < cs.size(); k++) {
Elf64_Rel* rel = (Elf64_Rel*)(cs[k].rel_data.data());
@ -991,7 +999,7 @@ static void applyMapRelo(ifstream& elfFile, const vector<struct bpf_map_def>& md
int symIndex = ELF64_R_SYM(rel[i].r_info);
string symName;
int ret = getSymNameByIdx(elfFile, symIndex, symName);
int ret = elfObj.getSymNameByIdx(symIndex, symName);
if (ret) return;
// Find the map fd and apply relo
@ -1080,7 +1088,7 @@ static int validateProg(const borrowed_fd& fd, string& progPinLoc,
return 0;
}
static int loadCodeSections(const char* elfPath, vector<codeSection>& cs, const string& license,
static int loadCodeSections(ElfObject& elfObj, vector<codeSection>& cs, const string& license,
const unsigned int bpfloader_ver) {
unsigned kvers = kernelVersion();
@ -1152,7 +1160,7 @@ static int loadCodeSections(const char* elfPath, vector<codeSection>& cs, const
bool log_oneline = !strchr(log_buf, '\n');
ALOGD("BPF_PROG_LOAD call for %s (%s) returned '%s' fd: %d (%s)", elfPath,
ALOGD("BPF_PROG_LOAD call for %s (%s) returned '%s' fd: %d (%s)", elfObj.path,
cs[i].name.c_str(), log_oneline ? log_buf : "{multiline}",
fd.get(), !fd.ok() ? std::strerror(errno) : "ok");
@ -1327,12 +1335,10 @@ static int pinProgs(const struct bpf_object * obj,
}
static int loadProgByLibbpf(const char* const elfPath, const unsigned int bpfloader_ver) {
int ret;
ElfObject elfObj(elfPath);
vector<struct bpf_map_def> md;
vector<codeSection> cs;
ifstream elfFile(elfPath, ios::in | ios::binary);
if (!elfFile.is_open()) return -1;
int ret;
LIBBPF_OPTS(bpf_object_open_opts, opts,
.bpf_token_path = "",
@ -1341,13 +1347,13 @@ static int loadProgByLibbpf(const char* const elfPath, const unsigned int bpfloa
if (!obj) return -1;
auto objGuard = base::make_scope_guard([&obj] { bpf_object__close(obj); });
ret = readSectionByName(".android_maps", elfFile, md);
ret = elfObj.readSectionByName(".android_maps", md);
if (ret) return ret;
ret = prepareLoadMaps(obj, md, bpfloader_ver);
if (ret) return ret;
ret = readCodeSections(elfFile, cs);
ret = readCodeSections(elfObj, cs);
if (ret && ret != -ENOENT) return ret;
ret = prepareLoadProgs(obj, cs, bpfloader_ver);
@ -1366,16 +1372,14 @@ static int loadProgByLibbpf(const char* const elfPath, const unsigned int bpfloa
}
int loadProg(const char* const elfPath, const unsigned int bpfloader_ver) {
ElfObject elfObj(elfPath);
vector<char> license;
vector<codeSection> cs;
vector<struct bpf_map_def> md;
vector<unique_fd> mapFds;
int ret;
ifstream elfFile(elfPath, ios::in | ios::binary);
if (!elfFile.is_open()) return -1;
ret = readSectionByName("license", elfFile, license);
ret = elfObj.readSectionByName("license", license);
if (ret) {
ALOGE("Couldn't find license in %s", elfPath);
return ret;
@ -1386,11 +1390,11 @@ int loadProg(const char* const elfPath, const unsigned int bpfloader_ver) {
ALOGD("BpfLoader ver 0x%05x processing ELF object %s", bpfloader_ver, elfPath);
ret = readSectionByName(".android_maps", elfFile, md);
ret = elfObj.readSectionByName(".android_maps", md);
if (ret == -2) ret = 0; // -2 means there were no maps to read
if (ret) return ret;
ret = createMaps(elfFile, md, mapFds, bpfloader_ver);
ret = createMaps(elfObj, md, mapFds, bpfloader_ver);
if (ret) {
ALOGE("Failed to create maps: (ret=%d) in %s", ret, elfPath);
return ret;
@ -1399,16 +1403,16 @@ int loadProg(const char* const elfPath, const unsigned int bpfloader_ver) {
for (unsigned i = 0; i < mapFds.size(); i++)
ALOGV("map_fd found at %d is %d in %s", i, mapFds[i].get(), elfPath);
ret = readCodeSections(elfFile, cs);
ret = readCodeSections(elfObj, cs);
if (ret == -ENOENT) return 0;
if (ret) {
ALOGE("Couldn't read all code sections in %s", elfPath);
return ret;
}
applyMapRelo(elfFile, md, mapFds, cs);
applyMapRelo(elfObj, md, mapFds, cs);
ret = loadCodeSections(elfPath, cs, string(license.data()), bpfloader_ver);
ret = loadCodeSections(elfObj, cs, string(license.data()), bpfloader_ver);
if (ret) ALOGE("Failed to load programs, loadCodeSections ret=%d", ret);
return ret;