platform_system_bpf/libbpf_android/Loader.cpp
Joel Fernandes d76a2004a6 Initial checkin for relocatable ELF loader and link with bcc
* Wrote a relocatable ELF loader from scratch. The loader library loads
all objects in /system/etc/bpf/, parses and fixes up eBPF instructions
based on relocation information, creates maps and programs and pins
them. A single API call without arguments is made which results in this
operation. The API has all the information it needs from the filesystem
and the ELF objects, so there is no configuration stored in the code
calling the loader API essentially making it zero conf. Initially this
will be used by time_in_state statistics collection using tracepoints.
In the future, netd's eBPF C code should be rewritten to use this
infrastructure and the old ELF loader can be gotten rid off. This is the
first step.

* Link libbpf with libbpf_android which will come from the external/ bcc
project. This will be used for tracepoint and perf eBPF support. In the
future it can be used for other things as kernel eBPF support advances.

This patch will be merged only after bcc is cloned into external/ to
prevent build breakage.

Bug: 112334572
Change-Id: Ic0fd9504e18031132d40bb627c5e44058d59c9f8
Signed-off-by: Joel Fernandes <joelaf@google.com>
2018-12-17 22:27:56 +00:00

588 lines
16 KiB
C++

/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#define LOG_TAG "LibBpfLoader"
#include <errno.h>
#include <linux/bpf.h>
#include <linux/elf.h>
#include <log/log.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/utsname.h>
#include <unistd.h>
#include "LoaderUtils.h"
#include "include/libbpf_android.h"
#include <cstdlib>
#include <fstream>
#include <iostream>
#include <string>
#include <android-base/strings.h>
#define BPF_FS_PATH "/sys/fs/bpf/"
// Size of the BPF log buffer for verifier logging
#define BPF_LOAD_LOG_SZ 0x1ffff
using android::base::StartsWith;
using std::ifstream;
using std::ios;
using std::vector;
namespace android {
namespace bpf {
typedef struct {
const char* name;
enum bpf_prog_type type;
} sectionType;
/*
* Map section name prefixes to program types, the section name will be:
* SEC(<prefix>/<name-of-program>)
* For example:
* SEC("tracepoint/sched_switch_func") where sched_switch_funcs
* is the name of the program, and tracepoint is the type.
*/
sectionType sectionNameTypes[] = {
{ "kprobe", BPF_PROG_TYPE_KPROBE },
{ "tracepoint", BPF_PROG_TYPE_TRACEPOINT },
{ "skfilter", BPF_PROG_TYPE_SOCKET_FILTER },
{ "cgroupskb", BPF_PROG_TYPE_CGROUP_SKB },
/* End of table */
{ "END", BPF_PROG_TYPE_UNSPEC },
};
typedef struct {
enum bpf_prog_type type;
string name;
vector<char> data;
vector<char> rel_data;
int prog_fd; /* fd after loading */
} codeSection;
/* Common with the eBPF C program */
struct bpf_map_def {
enum bpf_map_type type;
unsigned int key_size;
unsigned int value_size;
unsigned int max_entries;
unsigned int map_flags;
unsigned int inner_map_idx;
unsigned int numa_node;
};
static int readElfHeader(ifstream& elfFile, Elf64_Ehdr* eh) {
elfFile.seekg(0);
if (elfFile.fail()) return -1;
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 entries, ret = 0;
ret = readSectionHeadersAll(elfFile, shTable);
if (ret) return ret;
entries = shTable.size();
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 = 0;
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 */
static int readSectionByName(const char* name, ifstream& elfFile, vector<char>& 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)) {
vector<char> dataTmp;
dataTmp.resize(shTable[i].sh_size);
elfFile.seekg(shTable[i].sh_offset);
if (elfFile.fail()) return -1;
if (!elfFile.read((char*)dataTmp.data(), shTable[i].sh_size)) return -1;
data = dataTmp;
return 0;
}
}
return -2;
}
static int readSectionByType(ifstream& elfFile, int type, vector<char>& data) {
int ret;
vector<Elf64_Shdr> shTable;
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;
vector<char> dataTmp;
dataTmp.resize(shTable[i].sh_size);
elfFile.seekg(shTable[i].sh_offset);
if (elfFile.fail()) return -1;
if (!elfFile.read((char*)dataTmp.data(), shTable[i].sh_size)) return -1;
data = dataTmp;
return 0;
}
return -2;
}
static bool symCompare(Elf64_Sym a, Elf64_Sym b) {
return (a.st_value < b.st_value);
}
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 enum bpf_prog_type getSectionType(string& name) {
for (int i = 0; sectionNameTypes[i].type != BPF_PROG_TYPE_UNSPEC; i++)
if (StartsWith(name, sectionNameTypes[i].name)) return sectionNameTypes[i].type;
return BPF_PROG_TYPE_UNSPEC;
}
/* If ever needed
static string getSectionName(enum bpf_prog_type type)
{
for (int i = 0; sectionNameTypes[i].type != BPF_PROG_TYPE_UNSPEC; i++)
if (sectionNameTypes[i].type == type)
return std::string(sectionNameTypes[i].name);
return NULL;
}
*/
static bool isRelSection(codeSection& cs, string& name) {
for (int i = 0; sectionNameTypes[i].type != BPF_PROG_TYPE_UNSPEC; i++) {
sectionType st = sectionNameTypes[i];
if (st.type != cs.type) continue;
if (StartsWith(name, std::string(".rel") + st.name + "/"))
return true;
else
return false;
}
return false;
}
/* Read a section by its index - for ex to get sec hdr strtab blob */
static int readCodeSections(ifstream& elfFile, vector<codeSection>& cs) {
vector<Elf64_Shdr> shTable;
int entries, ret = 0;
ret = readSectionHeadersAll(elfFile, shTable);
if (ret) return ret;
entries = shTable.size();
for (int i = 0; i < entries; i++) {
string name;
codeSection cs_temp;
cs_temp.type = BPF_PROG_TYPE_UNSPEC;
ret = getSymName(elfFile, shTable[i].sh_name, name);
if (ret) return ret;
enum bpf_prog_type ptype = getSectionType(name);
if (ptype != BPF_PROG_TYPE_UNSPEC) {
deslash(name);
cs_temp.type = ptype;
cs_temp.name = name;
ret = readSectionByIdx(elfFile, i, cs_temp.data);
if (ret) return ret;
ALOGD("Loaded code section %d (%s)\n", i, name.c_str());
}
/* Check for rel section */
if (cs_temp.data.size() > 0 && i < entries) {
ret = getSymName(elfFile, shTable[i + 1].sh_name, name);
if (ret) return ret;
if (isRelSection(cs_temp, name)) {
ret = readSectionByIdx(elfFile, i + 1, cs_temp.rel_data);
if (ret) return ret;
ALOGD("Loaded relo section %d (%s)\n", i, name.c_str());
}
}
if (cs_temp.data.size() > 0) {
cs.push_back(cs_temp);
ALOGD("Adding section %d to cs list\n", i);
}
}
return 0;
}
static int getSymNameByIdx(ifstream& elfFile, int index, string& name) {
vector<Elf64_Sym> symtab;
int ret = 0;
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 getMapNames(ifstream& elfFile, vector<string>& names) {
int ret;
string mapName;
vector<Elf64_Sym> symtab;
vector<Elf64_Shdr> shTable;
ret = readSymTab(elfFile, 1 /* sort */, symtab);
if (ret) return ret;
/* Get index of maps section */
ret = readSectionHeadersAll(elfFile, shTable);
if (ret) return ret;
int maps_idx = -1;
for (int i = 0; i < (int)shTable.size(); i++) {
ret = getSymName(elfFile, shTable[i].sh_name, mapName);
if (ret) return ret;
if (!mapName.compare("maps")) {
maps_idx = i;
break;
}
}
/* No maps found */
if (maps_idx == -1) {
ALOGE("No maps could be found in elf object\n");
return -1;
}
for (int i = 0; i < (int)symtab.size(); i++) {
if (symtab[i].st_shndx == maps_idx) {
string s;
ret = getSymName(elfFile, symtab[i].st_name, s);
if (ret) return ret;
names.push_back(s);
}
}
return 0;
}
static int createMaps(const char* elfPath, ifstream& elfFile, vector<int>& mapFds) {
int ret, fd;
vector<char> mdData;
vector<struct bpf_map_def> md;
vector<string> mapNames;
string fname = pathToFilename(string(elfPath), true);
ret = readSectionByName("maps", elfFile, mdData);
if (ret) return ret;
md.resize(mdData.size() / sizeof(struct bpf_map_def));
memcpy(md.data(), mdData.data(), mdData.size());
ret = getMapNames(elfFile, mapNames);
if (ret) return ret;
mapFds.resize(mapNames.size());
for (int i = 0; i < (int)mapNames.size(); i++) {
// Format of pin location is /sys/fs/bpf/map_<filename>_<mapname>
string mapPinLoc;
bool reuse = false;
mapPinLoc = string(BPF_FS_PATH) + "map_" + fname + "_" + string(mapNames[i]);
if (access(mapPinLoc.c_str(), F_OK) == 0) {
fd = bpf_obj_get(mapPinLoc.c_str());
ALOGD("bpf_create_map reusing map %s, ret: %d\n", mapNames[i].c_str(), fd);
reuse = true;
} else {
fd = bpf_create_map(md[i].type, mapNames[i].c_str(), md[i].key_size, md[i].value_size,
md[i].max_entries, md[i].map_flags);
ALOGD("bpf_create_map name %s, ret: %d\n", mapNames[i].c_str(), fd);
}
if (fd < 0) return fd;
if (fd == 0) return -EINVAL;
if (!reuse) {
ret = bpf_obj_pin(fd, mapPinLoc.c_str());
if (ret < 0) return ret;
}
mapFds[i] = fd;
}
return ret;
}
/* For debugging, dump all instructions */
static void dumpIns(char* ins, int size) {
for (int row = 0; row < size / 8; row++) {
ALOGE("%d: ", row);
for (int j = 0; j < 8; j++) {
ALOGE("%3x ", ins[(row * 8) + j]);
}
ALOGE("\n");
}
}
/* For debugging, dump all code sections from cs list */
static void dumpAllCs(vector<codeSection>& cs) {
for (int i = 0; i < (int)cs.size(); i++) {
ALOGE("Dumping cs %d, name %s\n", int(i), cs[i].name.c_str());
dumpIns((char*)cs[i].data.data(), cs[i].data.size());
ALOGE("-----------\n");
}
}
static void applyRelo(void* insnsPtr, Elf64_Addr offset, int fd) {
int insnIndex;
struct bpf_insn *insn, *insns;
insns = (struct bpf_insn*)(insnsPtr);
insnIndex = offset / sizeof(struct bpf_insn);
insn = &insns[insnIndex];
ALOGD(
"applying relo to instruction at byte offset: %d, \
insn offset %d , insn %lx\n",
(int)offset, (int)insnIndex, *(unsigned long*)insn);
if (insn->code != (BPF_LD | BPF_IMM | BPF_DW)) {
ALOGE("Dumping all instructions till ins %d\n", insnIndex);
ALOGE("invalid relo for insn %d: code 0x%x\n", insnIndex, insn->code);
dumpIns((char*)insnsPtr, (insnIndex + 3) * 8);
return;
}
insn->imm = fd;
insn->src_reg = BPF_PSEUDO_MAP_FD;
}
static void applyMapRelo(ifstream& elfFile, vector<int> mapFds, vector<codeSection>& cs) {
vector<string> mapNames;
int ret = getMapNames(elfFile, mapNames);
if (ret) return;
for (int k = 0; k != (int)cs.size(); k++) {
Elf64_Rel* rel = (Elf64_Rel*)(cs[k].rel_data.data());
int n_rel = cs[k].rel_data.size() / sizeof(*rel);
for (int i = 0; i < n_rel; i++) {
int symIndex = ELF64_R_SYM(rel[i].r_info);
string symName;
ret = getSymNameByIdx(elfFile, symIndex, symName);
if (ret) return;
/* Find the map fd and apply relo */
for (int j = 0; j < (int)mapNames.size(); j++) {
if (!mapNames[j].compare(symName)) {
applyRelo(cs[k].data.data(), rel[i].r_offset, mapFds[j]);
break;
}
}
}
}
}
static int loadCodeSections(const char* elfPath, vector<codeSection>& cs, string license) {
int ret, fd, kvers;
if ((kvers = getMachineKvers()) < 0) return -1;
string fname = pathToFilename(string(elfPath), true);
for (int i = 0; i < (int)cs.size(); i++) {
string progPinLoc;
bool reuse = false;
// Format of pin location is
// /sys/fs/bpf/prog_<filename>_<mapname>
progPinLoc = string(BPF_FS_PATH) + "prog_" + fname + "_" + cs[i].name;
if (access(progPinLoc.c_str(), F_OK) == 0) {
fd = bpf_obj_get(progPinLoc.c_str());
ALOGD("New bpf prog load reusing prog %s, ret: %d\n", cs[i].name.c_str(), fd);
reuse = true;
} else {
vector<char> log_buf(BPF_LOAD_LOG_SZ, 0);
fd = bpf_prog_load(cs[i].type, cs[i].name.c_str(), (struct bpf_insn*)cs[i].data.data(),
cs[i].data.size(), license.c_str(), kvers, 0,
log_buf.data(), log_buf.size());
ALOGD("New bpf core prog_load for %s (%s) returned: %d\n", elfPath, cs[i].name.c_str(),
fd);
if (fd <= 0)
ALOGE("bpf_prog_load: log_buf contents: %s\n", (char *)log_buf.data());
}
if (fd < 0) return fd;
if (fd == 0) return -EINVAL;
if (!reuse) {
ret = bpf_obj_pin(fd, progPinLoc.c_str());
if (ret < 0) return ret;
}
cs[i].prog_fd = fd;
}
return 0;
}
int loadProg(const char* elfPath) {
vector<char> license;
vector<codeSection> cs;
vector<int> mapFds;
int ret;
ifstream elfFile(elfPath, ios::in | ios::binary);
if (!elfFile.is_open()) return -1;
ret = readSectionByName("license", elfFile, license);
if (ret) {
ALOGE("Couldn't find license in %s\n", elfPath);
return ret;
} else {
ALOGD("Loading ELF object %s with license %s\n", elfPath, (char*)license.data());
}
ret = readCodeSections(elfFile, cs);
if (ret) {
ALOGE("Couldn't read all code sections in %s\n", elfPath);
return ret;
}
/* Just for future debugging */
if (0) dumpAllCs(cs);
ret = createMaps(elfPath, elfFile, mapFds);
if (ret) {
ALOGE("Failed to create maps: (ret=%d) in %s\n", ret, elfPath);
return ret;
}
for (int i = 0; i < (int)mapFds.size(); i++)
ALOGD("map_fd found at %d is %d in %s\n", i, mapFds[i], elfPath);
applyMapRelo(elfFile, mapFds, cs);
ret = loadCodeSections(elfPath, cs, string(license.data()));
if (ret) ALOGE("Failed to load programs, loadCodeSections ret=%d\n", ret);
return ret;
}
} // namespace bpf
} // namespace android