blob: a16e4d01bc8ae471987aa7cc28b8a18ffa09728d [file] [log] [blame]
// Copyright 2022 The Centipede Authors.
//
// 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
//
// https://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.
#include "./centipede/runner_dl_info.h"
#include <elf.h>
#include <link.h> // dl_iterate_phdr
#include <cinttypes>
#include <cstdio>
#include <cstring>
#include "./centipede/runner_utils.h"
namespace centipede {
namespace {
// Struct to pass to dl_iterate_phdr's callback.
struct DlCallbackParam {
// Full path to the instrumented library or nullptr for the main binary.
const char *dl_path_suffix;
// DlInfo to set on success.
DlInfo &result;
};
bool StringEndsWithSuffix(const char *string, const char *suffix) {
const char *pos = strstr(string, suffix);
if (pos == nullptr) return false;
return pos == string + strlen(string) - strlen(suffix);
}
int g_some_global; // Used in DlIteratePhdrCallback.
} // namespace
// See man dl_iterate_phdr.
// `param_voidptr` is cast to a `DlCallbackParam *param`.
// Looks for the dynamic library who's dlpi_name ends with
// `param->dl_path_suffix` or for the main binary if `param->dl_path_suffix ==
// nullptr`. The code assumes that the main binary is the first one to be
// iterated on. If the desired library is found, sets result.start_address and
// result.size, otherwise leaves result unchanged.
static int DlIteratePhdrCallback(struct dl_phdr_info *info, size_t size,
void *param_voidptr) {
constexpr bool kDlDebug = false; // we may want to make it a runtime flag.
DlCallbackParam *param = static_cast<DlCallbackParam *>(param_voidptr);
DlInfo &result = param->result;
RunnerCheck(!result.IsSet(), "result is already set");
// Skip uninteresting info.
if (param->dl_path_suffix != nullptr &&
!StringEndsWithSuffix(info->dlpi_name, param->dl_path_suffix)) {
return 0; // 0 indicates we want to see the other entries.
}
auto some_code_address = reinterpret_cast<uintptr_t>(DlIteratePhdrCallback);
auto some_global_address = reinterpret_cast<uintptr_t>(&g_some_global);
result.start_address = info->dlpi_addr;
// Iterate program headers.
for (int j = 0; j < info->dlpi_phnum; ++j) {
// We are only interested in "Loadable program segments".
const auto &phdr = info->dlpi_phdr[j];
if (phdr.p_type != PT_LOAD) continue;
// phdr.p_vaddr represents the offset of the segment from info->dlpi_addr.
// phdr.p_memsz is the segment size in bytes.
// Their sum is the offset of the end of the segment from info->dlpi_addr.
uintptr_t end_offset = phdr.p_vaddr + phdr.p_memsz;
// We compute result.size as the largest such offset.
if (result.size < end_offset) result.size = end_offset;
// phdr.p_flags indicates RWX access rights for the segment,
// e.g. `phdr.p_flags & PF_X` is non-zero if the segment is executable.
if constexpr (kDlDebug) {
char executable_bit = (phdr.p_flags & PF_X) ? 'X' : '-';
char writable_bit = (phdr.p_flags & PF_W) ? 'W' : '-';
char readable_bit = (phdr.p_flags & PF_R) ? 'R' : '-';
fprintf(stderr,
"%s: segment [%d] name: %s addr: %" PRIx64 " size: %" PRIu64
" flags: %c%c%c\n",
__func__, j, info->dlpi_name, phdr.p_vaddr, phdr.p_memsz,
executable_bit, writable_bit, readable_bit);
}
}
if constexpr (kDlDebug) {
fprintf(stderr,
"%s: name: %s addr: %" PRIx64 " size: %" PRIu64
" addr+size: %" PRIx64 " code: %" PRIx64 " global: %" PRIx64 "\n",
__func__, info->dlpi_name, info->dlpi_addr, result.size,
info->dlpi_addr + result.size, some_code_address,
some_global_address);
}
RunnerCheck(result.size != 0,
"DlIteratePhdrCallback failed to compute result.size");
if (param->dl_path_suffix == nullptr) {
// When the main binary is coverage-instrumented, we currently only support
// statically linking this runner. Which means, that the runner itself
// is part of the main binary, and we can do additional checks, which we
// can't do if the runner is a separate library.
RunnerCheck(result.InBounds(some_code_address),
"DlIteratePhdrCallback: a sample code address is not in bounds "
"of main executable");
RunnerCheck(result.InBounds(some_global_address),
"DlIteratePhdrCallback: a sample global address is not in "
"bounds of main executable");
}
return result.IsSet(); // return 1 if we found what we were looking for.
}
DlInfo GetDlInfo(const char *dl_path_suffix) {
DlInfo result;
DlCallbackParam callback_param = {dl_path_suffix, result};
dl_iterate_phdr(DlIteratePhdrCallback, &callback_param);
return result;
}
} // namespace centipede