Skip to content

Commit

Permalink
Support address lookup for zipped libs.
Browse files Browse the repository at this point in the history
This commit enables code operating on elf files in bcc_elf.c to open elf
files stored without compression in a zip archive. Elf files stored in
zip archives are refered to by a special path consisting of the archive
path followed by "!/" and name of the entry within the archive. This is
the convention used by Android.
  • Loading branch information
michalgr committed Feb 17, 2023
1 parent caa1d11 commit bef3d74
Show file tree
Hide file tree
Showing 5 changed files with 161 additions and 39 deletions.
92 changes: 70 additions & 22 deletions src/cc/bcc_elf.c
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
#include "bcc_elf.h"
#include "bcc_proc.h"
#include "bcc_syms.h"
#include "bcc_zip.h"

#define NT_STAPSDT 3
#define ELF_ST_TYPE(x) (((uint32_t) x) & 0xf)
Expand All @@ -50,7 +51,6 @@ static int openelf_fd(int fd, Elf **elf_out) {
return 0;
}

#ifdef HAVE_LIBLZMA
static int openelf_mem(void *buf, size_t buf_len, Elf **elf_out) {
if (elf_version(EV_CURRENT) == EV_NONE)
return -1;
Expand All @@ -61,29 +61,50 @@ static int openelf_mem(void *buf, size_t buf_len, Elf **elf_out) {

return 0;
}
#endif

// Type of bcc_elf_file.
enum bcc_elf_file_type {
// Empty bcc_elf_file, not associated with any Elf or resources.
// None of the union fields should be read and elf pointer is NULL.
BCC_ELF_FILE_TYPE_NONE = 0,

// bcc_elf_file owning a file descriptor and providing access to
// an Elf file backed by data from that file.
// fd field of the impl union stores the file descriptor and elf
// pointer is not NULL.
BCC_ELF_FILE_TYPE_FD,

// bcc_elf_file owning a memory buffer and providing access to an
// Elf file backed by data in that buffer.
// buf field of the impl union stores the address of the buffer
// and elf pointer is not NULL.
BCC_ELF_FILE_TYPE_BUF,

// bcc_elf_file owning a bcc_zip_archive and providing access to
// an Elf file backed by data from one of the zip entries.
// archive field of the impl union stores the address of the
// zip archive and elf pointer is not NULL.
BCC_ELF_FILE_TYPE_ARCHIVE
};

// Provides access to an Elf structure in an uniform way,
// independently from its source (file or memory buffer).
// independently from its source (file, memory buffer, zip archive).
struct bcc_elf_file {
Elf *elf;
enum bcc_elf_file_type type;

// Set only when the elf file is parsed from an opened file descriptor that
// needs to be closed. Otherwise set to -1.
int fd;

// Set only when the elf file is parsed from a memory buffer that needs to be
// freed.
void *buf;
union {
int fd;
void *buf;
struct bcc_zip_archive *archive;
};
};

// Initializes bcc_elf_file as not pointing to any elf file and not
// owning any resources. After returning the provided elf_file can be
// safely passed to bcc_elf_file_close.
static void bcc_elf_file_init(struct bcc_elf_file *elf_file) {
elf_file->elf = NULL;
elf_file->fd = -1;
elf_file->buf = NULL;
memset(elf_file, 0, sizeof(struct bcc_elf_file));
}

#ifdef HAVE_LIBLZMA
Expand All @@ -96,6 +117,7 @@ static int bcc_elf_file_open_buf(void *buf, size_t buf_len,
}

out->elf = elf;
out->type = BCC_ELF_FILE_TYPE_BUF;
out->buf = buf;
return 0;
}
Expand All @@ -109,36 +131,62 @@ static int bcc_elf_file_open_fd(int fd, struct bcc_elf_file *out) {
}

out->elf = elf;
out->type = BCC_ELF_FILE_TYPE_FD;
out->fd = fd;
return 0;
}

static int bcc_elf_file_open(const char *path, struct bcc_elf_file *out) {
struct bcc_zip_archive *archive = NULL;
struct bcc_zip_entry entry;
int fd = -1;

fd = open(path, O_RDONLY);
if (fd < 0)
return -1;
if (fd >= 0) {
if (bcc_elf_file_open_fd(fd, out)) {
close(fd);
return -1;
}

if (bcc_elf_file_open_fd(fd, out)) {
close(fd);
return -1;
return 0;
}

return 0;
archive = bcc_zip_archive_open_and_find(path, &entry);
if (archive) {
if (entry.compression ||
openelf_mem((void *)entry.data, entry.data_length, &out->elf)) {
bcc_zip_archive_close(archive);
return -1;
}

out->type = BCC_ELF_FILE_TYPE_ARCHIVE;
out->archive = archive;
return 0;
}

return -1;
}

static void bcc_elf_file_close(struct bcc_elf_file *elf_file) {
if (elf_file->elf) {
elf_end(elf_file->elf);
}

if (elf_file->fd >= 0) {
switch (elf_file->type) {
case BCC_ELF_FILE_TYPE_FD:
close(elf_file->fd);
}
break;

if (elf_file->buf) {
case BCC_ELF_FILE_TYPE_BUF:
free(elf_file->buf);
break;

case BCC_ELF_FILE_TYPE_ARCHIVE:
bcc_zip_archive_close(elf_file->archive);
break;

default:
break;
}

bcc_elf_file_init(elf_file);
Expand Down
25 changes: 25 additions & 0 deletions src/cc/bcc_zip.c
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include "bcc_zip.h"

#include <fcntl.h>
#include <limits.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
Expand Down Expand Up @@ -386,3 +387,27 @@ int bcc_zip_archive_find_entry_at_offset(struct bcc_zip_archive* archive,

return -1;
}

struct bcc_zip_archive* bcc_zip_archive_open_and_find(
const char* path, struct bcc_zip_entry* out) {
struct bcc_zip_archive* archive = NULL;
const char* separator = strstr(path, "!/");
if (separator == NULL || separator - path >= PATH_MAX) {
return NULL;
}

char archive_path[PATH_MAX];
strncpy(archive_path, path, separator - path);
archive_path[separator - path] = 0;
archive = bcc_zip_archive_open(archive_path);
if (archive == NULL) {
return NULL;
}

if (bcc_zip_archive_find_entry(archive, separator + 2, out)) {
bcc_zip_archive_close(archive);
return NULL;
}

return archive;
}
7 changes: 7 additions & 0 deletions src/cc/bcc_zip.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,13 @@ int bcc_zip_archive_find_entry_at_offset(struct bcc_zip_archive* archive,
uint32_t offset,
struct bcc_zip_entry* out);

// Opens a zip archives and looks up entry within the archive.
// Provided path is interpreted as archive path followed by "!/"
// characters and name of the zip entry. This convention is used
// by Android tools.
struct bcc_zip_archive* bcc_zip_archive_open_and_find(
const char* path, struct bcc_zip_entry* out);

#ifdef __cplusplus
}
#endif
Expand Down
12 changes: 12 additions & 0 deletions tests/cc/test_c_api.cc
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,18 @@ TEST_CASE("resolve symbol name in external library using loaded libraries", "[c_
bcc_procutils_free(sym.module);
}

TEST_CASE("resolve symbol name in external zipped library", "[c_api]") {
struct bcc_symbol sym;
std::string lib_path =
CMAKE_CURRENT_BINARY_DIR "/archive.zip!/libdebuginfo_test_lib.so";

REQUIRE(bcc_resolve_symname(lib_path.c_str(), "symbol", 0x0, 0, nullptr,
&sym) == 0);
REQUIRE(sym.module == lib_path);
REQUIRE(sym.offset != 0);
bcc_procutils_free(sym.module);
}

namespace {

void system(const std::string &command) {
Expand Down
64 changes: 47 additions & 17 deletions tests/cc/test_zip.cc
Original file line number Diff line number Diff line change
Expand Up @@ -25,33 +25,41 @@

namespace {

bcc_zip_entry get_uncompressed_entry(bcc_zip_archive* archive,
const char* asset_name) {
void require_entry_name_is(const bcc_zip_entry& entry, const char* name) {
REQUIRE(entry.name_length == strlen(name));
REQUIRE(memcmp(entry.name, name, strlen(name)) == 0);
}

bcc_zip_entry get_required_entry(bcc_zip_archive* archive,
const char* asset_name) {
bcc_zip_entry out;
REQUIRE(bcc_zip_archive_find_entry(archive, asset_name, &out) == 0);

REQUIRE(out.compression == 0);
REQUIRE(out.name_length == strlen(asset_name));
REQUIRE(memcmp(out.name, asset_name, strlen(asset_name)) == 0);
REQUIRE(out.data_offset > 0);

require_entry_name_is(out, asset_name);
return out;
}

const void* get_uncompressed_data(const bcc_zip_entry& entry) {
REQUIRE(entry.compression == 0);
REQUIRE(entry.data_offset > 0);
REQUIRE(entry.data != nullptr);
return entry.data;
}

} // namespace

TEST_CASE("finds entries in a zip archive by name", "[zip]") {
bcc_zip_archive* archive = bcc_zip_archive_open(TEST_ARCHIVE_PATH);
REQUIRE(archive != nullptr);

bcc_zip_entry entry = get_uncompressed_entry(archive, LIB_ENTRY_NAME);
REQUIRE(memcmp(entry.data,
bcc_zip_entry entry = get_required_entry(archive, LIB_ENTRY_NAME);
REQUIRE(memcmp(get_uncompressed_data(entry),
"\x7f"
"ELF",
4) == 0);

entry = get_uncompressed_entry(archive, ENTRY_IN_SUBDIR_NAME);
REQUIRE(memcmp(entry.data, "This is a text file\n", 20) == 0);
entry = get_required_entry(archive, ENTRY_IN_SUBDIR_NAME);
REQUIRE(memcmp(get_uncompressed_data(entry), "This is a text file\n", 20) ==
0);

REQUIRE(bcc_zip_archive_find_entry(archive, "missing", &entry) == -1);

Expand All @@ -64,11 +72,8 @@ TEST_CASE("finds entries in a zip archive by offset", "[zip]") {

bcc_zip_entry entry;
REQUIRE(bcc_zip_archive_find_entry_at_offset(archive, 100, &entry) == 0);
REQUIRE(entry.compression == 0);
REQUIRE(entry.name_length == strlen(LIB_ENTRY_NAME));
REQUIRE(memcmp(entry.name, LIB_ENTRY_NAME, strlen(LIB_ENTRY_NAME)) == 0);
REQUIRE(entry.data_offset > 0);
REQUIRE(memcmp(entry.data,
require_entry_name_is(entry, LIB_ENTRY_NAME);
REQUIRE(memcmp(get_uncompressed_data(entry),
"\x7f"
"ELF",
4) == 0);
Expand All @@ -77,3 +82,28 @@ TEST_CASE("finds entries in a zip archive by offset", "[zip]") {

bcc_zip_archive_close(archive);
}

TEST_CASE("open zip archive and finds an entry", "[zip]") {
bcc_zip_entry entry;
bcc_zip_archive* archive = bcc_zip_archive_open_and_find(
TEST_ARCHIVE_PATH "!/" LIB_ENTRY_NAME, &entry);
REQUIRE(archive != nullptr);
require_entry_name_is(entry, LIB_ENTRY_NAME);
REQUIRE(memcmp(get_uncompressed_data(entry),
"\x7f"
"ELF",
4) == 0);
bcc_zip_archive_close(archive);

archive = bcc_zip_archive_open_and_find(
TEST_ARCHIVE_PATH "!/" ENTRY_IN_SUBDIR_NAME, &entry);
REQUIRE(archive != nullptr);
require_entry_name_is(entry, ENTRY_IN_SUBDIR_NAME);
REQUIRE(memcmp(get_uncompressed_data(entry), "This is a text file\n", 20) ==
0);
bcc_zip_archive_close(archive);

archive =
bcc_zip_archive_open_and_find(TEST_ARCHIVE_PATH "!/NOT_FOUND", &entry);
REQUIRE(archive == nullptr);
}

0 comments on commit bef3d74

Please sign in to comment.