#include <config.h>
#include <assert.h>
#include <stdio.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
#ifdef HAVE_ELF_H
#include <elf.h>
#else
#define ELFMAG "\177ELF"
#define SELFMAG 4
#endif
#ifdef __APPLE__
#include <mach-o/fat.h>
#include <mach-o/loader.h>
#else
#include "mach_o_stub.h"
#endif
#include <sys/stat.h>
#ifdef HAVE_SYS_MMAN_H
#include <sys/mman.h>
#endif
#include "byte_swapping.h"
#include "trace.h"
#include "fix_debug_info.h"
#ifndef SHN_XINDEX
#define SHN_XINDEX SHN_HIRESERVE
#endif
static void *mmap_file(const char *path, int *p_fd, struct stat *st) {
int fd;
void *base;
fd = open(path, O_RDWR);
if (fd < 0) {
rs_log_error("error opening file '%s': %s", path, strerror(errno));
return NULL;
}
if (fstat(fd, st) != 0) {
rs_log_error("fstat of file '%s' failed: %s", path, strerror(errno));
close(fd);
return NULL;
}
if (st->st_size <= 0) {
rs_log_error("file '%s' has invalid file type or size", path);
close(fd);
return NULL;
}
#ifdef HAVE_MMAP
base = mmap(NULL, st->st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (base == MAP_FAILED) {
rs_log_error("mmap of file '%s' failed: %s", path, strerror(errno));
close(fd);
return NULL;
}
#else
base = malloc(st->st_size);
if (base == NULL) {
rs_log_error("can't allocate buffer for %s: malloc failed", path);
close(fd);
return NULL;
}
errno = 0;
if (read(fd, base, st->st_size) != st->st_size) {
rs_log_error("can't read %ld bytes from %s: %s", (long) st->st_size, path,
strerror(errno));
close(fd);
return NULL;
}
#endif
*p_fd = fd;
return base;
}
static int munmap_file(void *base, const char *path, int fd,
const struct stat *st) {
int status = 0;
#ifdef HAVE_MMAP
if (munmap(base, st->st_size) != 0) {
rs_log_error("munmap of file '%s' failed: %s", path, strerror(errno));
status = 1;
}
#else
errno = 0;
if (lseek(fd, 0, SEEK_SET) == -1) {
rs_log_error("can't seek to start of %s: %s", path, strerror(errno));
status = 1;
} else if (write(fd, base, st->st_size) != st->st_size) {
rs_log_error("can't write %ld bytes to %s: %s", (long) st->st_size, path,
strerror(errno));
status = 1;
}
#endif
if (close(fd) != 0) {
rs_log_error("close of file '%s' failed: %s", path, strerror(errno));
status = 1;
}
return status;
}
static int replace_string(void *base, size_t size,
const char *search, const char *replace) {
char *start = (char *) base;
char *end = (char *) base + size;
int count = 0;
char *p;
size_t search_len = strlen(search);
size_t replace_len = strlen(replace);
assert(replace_len == search_len);
if (size < search_len + 1)
return 0;
for (p = start; p < end - search_len - 1; p++) {
if ((*p == *search) && !memcmp(p, search, search_len)) {
memcpy(p, replace, replace_len);
count++;
}
}
return count;
}
#ifdef HAVE_ELF_H
static int FindElfSection(const void *elf_mapped_base, off_t elf_size,
const char *desired_section_name,
const void **section_start, int *section_size) {
const unsigned char *elf_base = (const unsigned char *) elf_mapped_base;
const Elf32_Ehdr *elf32_header = (const Elf32_Ehdr *) (const void *) elf_base;
unsigned int i;
unsigned int num_sections;
assert(elf_mapped_base);
assert(section_start);
assert(section_size);
*section_start = NULL;
*section_size = 0;
#if WORDS_BIGENDIAN
if (elf32_header->e_ident[EI_DATA] != ELFDATA2MSB) {
rs_trace("sorry, not fixing debug info: "
"distcc server host is big-endian, object file is not");
return 0;
}
#else
if (elf32_header->e_ident[EI_DATA] != ELFDATA2LSB) {
rs_trace("sorry, not fixing debug info: "
"distcc server host is little-endian, object file is not");
return 0;
}
#endif
if (elf32_header->e_ident[EI_CLASS] == ELFCLASS32) {
const Elf32_Ehdr *elf_header = elf32_header;
const Elf32_Shdr *sections =
(const Elf32_Shdr *) (const void *) (elf_base + elf_header->e_shoff);
const Elf32_Shdr *string_section = sections + elf_header->e_shstrndx;
const Elf32_Shdr *desired_section = NULL;
if (elf_size < (off_t) sizeof(*elf_header)) {
rs_trace("object file is too small for ELF header; maybe got truncated?");
return 0;
}
if (elf_header->e_shoff <= 0 ||
elf_header->e_shoff > elf_size - sizeof(Elf32_Shdr)) {
rs_trace("invalid e_shoff value in ELF header");
return 0;
}
if (elf_header->e_shstrndx == SHN_UNDEF) {
rs_trace("object file has no section name string table"
" (e_shstrndx == SHN_UNDEF)");
return 0;
}
if (elf_header->e_shstrndx == SHN_XINDEX) {
string_section = sections + sections[0].sh_link;
}
num_sections = elf_header->e_shnum;
if (num_sections == 0) {
num_sections = sections[0].sh_size;
}
for (i = 0; i < num_sections; ++i) {
const char *section_name = (char *)(elf_base +
string_section->sh_offset +
sections[i].sh_name);
if (!strcmp(section_name, desired_section_name)) {
desired_section = §ions[i];
break;
}
}
if (desired_section != NULL && desired_section->sh_size > 0) {
int desired_section_size = desired_section->sh_size;
*section_start = elf_base + desired_section->sh_offset;
*section_size = desired_section_size;
return 1;
} else {
return 0;
}
} else if (elf32_header->e_ident[EI_CLASS] == ELFCLASS64) {
const Elf64_Ehdr *elf_header = (const Elf64_Ehdr *) (const void *) elf_base;
const Elf64_Shdr *sections =
(const Elf64_Shdr *) (const void *) (elf_base + elf_header->e_shoff);
const Elf64_Shdr *string_section = sections + elf_header->e_shstrndx;
const Elf64_Shdr *desired_section = NULL;
if (elf_size < (off_t) sizeof(*elf_header)) {
rs_trace("object file is too small for ELF header; maybe got truncated?");
return 0;
}
if (elf_header->e_shoff <= 0 ||
elf_header->e_shoff > (size_t) elf_size - sizeof(Elf64_Shdr)) {
rs_trace("invalid e_shoff value in ELF header");
return 0;
}
if (elf_header->e_shstrndx == SHN_UNDEF) {
rs_trace("object file has no section name string table"
" (e_shstrndx == SHN_UNDEF)");
return 0;
}
if (elf_header->e_shstrndx == SHN_XINDEX) {
string_section = sections + sections[0].sh_link;
}
num_sections = elf_header->e_shnum;
if (num_sections == 0) {
num_sections = sections[0].sh_size;
}
for (i = 0; i < num_sections; ++i) {
const char *section_name = (char*)(elf_base +
string_section->sh_offset +
sections[i].sh_name);
if (!strcmp(section_name, desired_section_name)) {
desired_section = §ions[i];
break;
}
}
if (desired_section != NULL && desired_section->sh_size > 0) {
int desired_section_size = desired_section->sh_size;
*section_start = elf_base + desired_section->sh_offset;
*section_size = desired_section_size;
return 1;
} else {
return 0;
}
} else {
rs_trace("unknown ELF class - neither ELFCLASS32 nor ELFCLASS64");
return 0;
}
}
static void update_elf_section(const char *path,
const void *base,
off_t size,
const char *desired_section_name,
const char *search,
const char *replace) {
const void *desired_section = NULL;
int desired_section_size = 0;
if (FindElfSection(base, size, desired_section_name,
&desired_section, &desired_section_size)
&& desired_section_size > 0) {
void *desired_section_rw = (void *) desired_section;
int count = replace_string(desired_section_rw, desired_section_size,
search, replace);
if (count == 0) {
rs_trace("\"%s\" section of file %s has no occurrences of \"%s\"",
desired_section_name, path, search);
} else {
rs_log_info("updated \"%s\" section of file \"%s\": "
"replaced %d occurrences of \"%s\" with \"%s\"",
desired_section_name, path, count, search, replace);
if (count > 1) {
rs_log_warning("only expected to replace one occurrence!");
}
}
} else {
rs_trace("file %s has no \"%s\" section", path, desired_section_name);
}
}
#endif
static void update_debug_info_elf(const char *path,
const void *base, off_t size,
const char *search, const char *replace) {
#ifndef HAVE_ELF_H
(void) base;
(void) size;
rs_trace("no <elf.h>, so can't change %s to %s in debug info for %s",
search, replace, path);
#else
update_elf_section(path, base, size, ".debug_info", search, replace);
update_elf_section(path, base, size, ".debug_str", search, replace);
#endif
}
static int FindMachOSection(const void *base, off_t size,
int want_symtab,
const char *desired_segment_name,
const char *desired_section_name,
int swap, int macho_bits,
const void **section_start, int *section_size) {
assert(base);
assert(section_start);
assert(section_size);
const uint8_t *mapped_base = (const uint8_t *)base;
const uint8_t *mapped_end = mapped_base + size;
if (mapped_end < mapped_base) {
rs_trace("object file has to be kidding");
return 0;
}
*section_start = NULL;
*section_size = 0;
uint32_t ncmds;
uint32_t sizeofcmds;
const struct load_command *load_command_base;
const uint8_t *load_command_end;
if (macho_bits == 32) {
const struct mach_header* header = (const struct mach_header*)mapped_base;
if ((size_t)size < sizeof(*header)) {
rs_trace("object file is too small for Mach-O 32 header");
return 0;
}
ncmds = maybe_swap_32(swap, header->ncmds);
sizeofcmds = maybe_swap_32(swap, header->sizeofcmds);
load_command_base = (struct load_command*)(mapped_base + sizeof(*header));
load_command_end = mapped_base + sizeof(*header) + sizeofcmds - 1;
} else {
const struct mach_header_64* header =
(const struct mach_header_64*)mapped_base;
if ((size_t)size < sizeof(*header)) {
rs_trace("object file is too small for Mach-O 64 header");
return 0;
}
ncmds = maybe_swap_32(swap, header->ncmds);
sizeofcmds = maybe_swap_32(swap, header->sizeofcmds);
load_command_base = (struct load_command*)(mapped_base + sizeof(*header));
load_command_end = mapped_base + sizeof(*header) + sizeofcmds;
}
if (load_command_end > mapped_end ||
load_command_end < (const uint8_t *)load_command_base) {
rs_trace("object file is too small for load commands");
return 0;
}
const struct load_command *command = load_command_base;
uint32_t cumulative_cmdsizes = 0;
uint32_t command_index;
for (command_index = 0; command_index < ncmds; ++command_index) {
uint32_t cmd = maybe_swap_32(swap, command->cmd);
uint32_t cmdsize = maybe_swap_32(swap, command->cmdsize);
uint32_t new_cumulative_cmdsizes = cumulative_cmdsizes + cmdsize;
if (new_cumulative_cmdsizes > sizeofcmds ||
new_cumulative_cmdsizes < cumulative_cmdsizes) {
rs_trace("load command size exceeds declared size of all commands");
return 0;
}
cumulative_cmdsizes = new_cumulative_cmdsizes;
uint32_t sect_off;
uint32_t sect_size;
int found_sect = 0;
if (!want_symtab) {
if (cmd == LC_SEGMENT) {
if (macho_bits != 32) {
rs_trace("32-bit segment command found in non-32-bit file");
return 0;
}
const struct segment_command* sc =
(const struct segment_command*)command;
uint32_t nsects = maybe_swap_32(swap, sc->nsects);
const struct section* sect_base =
(const struct section*)((const uint8_t*)sc + sizeof(*sc));
if (sizeof(*sc) + sizeof(*sect_base) * nsects > cmdsize) {
rs_trace("32-bit segment command overflows space allocated for it");
return 0;
}
uint32_t sect_index;
for (sect_index = 0; sect_index < nsects; ++sect_index) {
if (!strncmp(sect_base[sect_index].sectname, desired_section_name,
sizeof(sect_base[sect_index].sectname)) &&
!strncmp(sect_base[sect_index].segname, desired_segment_name,
sizeof(sect_base[sect_index].segname))) {
sect_size = maybe_swap_32(swap, sect_base[sect_index].size);
sect_off = maybe_swap_32(swap, sect_base[sect_index].offset);
found_sect = 1;
break;
}
}
} else if (cmd == LC_SEGMENT_64) {
if (macho_bits != 64) {
rs_trace("64-bit segment command found in non-64-bit file");
return 0;
}
const struct segment_command_64* sc =
(const struct segment_command_64*)command;
uint32_t nsects = maybe_swap_32(swap, sc->nsects);
const struct section_64* sect_base =
(const struct section_64*)((const uint8_t*)sc + sizeof(*sc));
if (sizeof(*sc) + sizeof(*sect_base) * nsects > cmdsize) {
rs_trace("64-bit segment command overflows space allocated for it");
return 0;
}
uint32_t sect_index;
for (sect_index = 0; sect_index < nsects; ++sect_index) {
if (!strncmp(sect_base[sect_index].sectname, desired_section_name,
sizeof(sect_base[sect_index].sectname)) &&
!strncmp(sect_base[sect_index].segname, desired_segment_name,
sizeof(sect_base[sect_index].sectname))) {
uint64_t sect_size_64 =
maybe_swap_64(swap, sect_base[sect_index].size);
if (sect_size_64 > UINT32_MAX) {
rs_trace("a section can't possibly be this big");
return 0;
}
sect_size = (uint32_t)sect_size_64;
sect_off = maybe_swap_32(swap, sect_base[sect_index].offset);
found_sect = 1;
break;
}
}
}
} else if (cmd == LC_SYMTAB) {
const struct symtab_command* sc = (const struct symtab_command*)command;
sect_off = maybe_swap_32(swap, sc->stroff);
sect_size = maybe_swap_32(swap, sc->strsize);
found_sect = 1;
}
if (found_sect) {
const uint8_t *section_base = mapped_base + sect_off;
const uint8_t *section_end = section_base + sect_size;
if (section_base < mapped_base ||
section_end > mapped_end || section_end < section_base) {
rs_trace("object file is too small for section");
return 0;
}
*section_start = (const void*)section_base;
*section_size = sect_size;
return 1;
}
command = (const struct load_command*)((const uint8_t*)command + cmdsize);
}
return 0;
}
static void update_macho_section(const char *path,
const void *base,
off_t size,
int want_symtab,
const char *desired_segment_name,
const char *desired_section_name,
const char *search,
const char *replace,
int swap, int macho_bits) {
struct section a_sect;
char region_name[sizeof(a_sect.segname) + sizeof(a_sect.sectname) + 10];
const void *section = NULL;
int section_size = 0;
if (want_symtab) {
strncpy(region_name, "LC_SYMTAB string table", sizeof(region_name));
region_name[sizeof(region_name) - 1] = '\0';
} else {
snprintf(region_name, sizeof(region_name), "%s,%s section",
desired_segment_name, desired_section_name);
}
if (!FindMachOSection(base, size,
want_symtab, desired_segment_name, desired_section_name,
swap, macho_bits, §ion, §ion_size)) {
rs_trace("file \"%s\" has no %s", path, region_name);
return;
}
void *section_rw = (void*)section;
int count = replace_string(section_rw, section_size, search, replace);
if (count == 0) {
rs_trace("%s of file \"%s\" has no occurences of \"%s\"",
region_name, path, search);
} else {
rs_log_info("updated %s of file \"%s\": "
"replaced %d occurrence%s of of \"%s\" with \"%s\"",
region_name, path, count, (count == 1) ? "" : "s",
search, replace);
}
}
static void update_debug_info_macho_thin(const char *path,
const void *base, off_t size,
const char *search,
const char *replace) {
if ((size_t)size < sizeof(uint32_t)) {
rs_trace("object file has no magic, blacklisted by Magician's Alliance?");
return;
}
uint32_t magic = *(uint32_t*)base;
int swap = 0;
if (magic == MH_CIGAM || magic == MH_CIGAM_64)
swap = 1;
int macho_bits = 32;
if (magic == MH_MAGIC_64 || magic == MH_CIGAM_64)
macho_bits = 64;
update_macho_section(path, base, size, 0, "__DWARF", "__debug_info",
search, replace, swap, macho_bits);
update_macho_section(path, base, size, 0, "__DWARF", "__debug_str",
search, replace, swap, macho_bits);
update_macho_section(path, base, size, 1, "", "",
search, replace, swap, macho_bits);
}
static void update_debug_info_macho_fat(const char *path,
const void *base, off_t size,
const char *search,
const char *replace) {
const uint8_t *mapped_base = (const uint8_t*)base;
const uint8_t *mapped_end = mapped_base + size;
if (mapped_end < mapped_base) {
rs_trace("object file can't possibly be serious");
return;
}
if ((size_t)size < sizeof(struct fat_header)) {
rs_trace("object file too small for fat header");
return;
}
const struct fat_header *fat = (const struct fat_header *)base;
uint32_t nfat_arch = swap_big_to_cpu_32(fat->nfat_arch);
if (sizeof(*fat) + sizeof(struct fat_arch) * nfat_arch > (size_t)size) {
rs_trace("object file too small for all of this fat");
return;
}
const struct fat_arch *fat_arch_base =
(const struct fat_arch *)(mapped_base + sizeof(*fat));
uint32_t fat_index;
for (fat_index = 0; fat_index < nfat_arch; ++fat_index) {
uint32_t arch_off = swap_big_to_cpu_32(fat_arch_base[fat_index].offset);
uint32_t arch_size = swap_big_to_cpu_32(fat_arch_base[fat_index].size);
if (arch_size == 0) {
rs_trace("empty architecture");
continue;
}
const uint8_t *arch_base = mapped_base + arch_off;
const uint8_t *arch_end = arch_base + arch_size;
if (arch_base < mapped_base ||
arch_end > mapped_end || arch_end < arch_base) {
rs_trace("arch is too big for its fat, if you would believe that");
return;
}
update_debug_info_macho_thin(path, arch_base, arch_size, search, replace);
}
}
static int update_debug_info(const char *path, const char *search,
const char *replace) {
struct stat st;
int fd;
void *base;
base = mmap_file(path, &fd, &st);
if (base == NULL) {
return 0;
}
if (st.st_size >= SELFMAG && memcmp(base, ELFMAG, SELFMAG) == 0) {
update_debug_info_elf(path, base, st.st_size, search, replace);
} else if ((size_t)st.st_size >= sizeof(uint32_t) &&
(*(uint32_t*)base == MH_MAGIC || *(uint32_t*)base == MH_CIGAM ||
*(uint32_t*)base == MH_MAGIC_64 ||
*(uint32_t*)base == MH_CIGAM_64)) {
update_debug_info_macho_thin(path, base, st.st_size, search, replace);
} else if ((size_t)st.st_size >= sizeof(uint32_t) &&
swap_big_to_cpu_32(*(uint32_t*)base) == FAT_MAGIC) {
update_debug_info_macho_fat(path, base, st.st_size, search, replace);
} else {
rs_trace("unknown object file format");
}
return munmap_file(base, path, fd, &st);
}
int dcc_fix_debug_info(const char *path, const char *client_path,
const char *server_path)
{
size_t client_path_len = strlen(client_path);
size_t server_path_len = strlen(server_path);
assert(client_path_len <= server_path_len);
char *client_path_plus_slashes = malloc(server_path_len + 1);
if (!client_path_plus_slashes) {
rs_log_crit("failed to allocate memory");
return 1;
}
strcpy(client_path_plus_slashes, client_path);
while (client_path_len < server_path_len) {
client_path_plus_slashes[client_path_len++] = '/';
}
client_path_plus_slashes[client_path_len] = '\0';
rs_log_info("client_path_plus_slashes = %s", client_path_plus_slashes);
return update_debug_info(path, server_path, client_path_plus_slashes);
}
#ifdef TEST
const char *rs_program_name;
int main(int argc, char **argv) {
rs_program_name = argv[0];
rs_add_logger(rs_logger_file, RS_LOG_DEBUG, NULL, STDERR_FILENO);
rs_trace_set_level(RS_LOG_DEBUG);
if (argc != 4) {
rs_log_error("Usage: %s <filename> <client-path> <server-path>",
rs_program_name);
exit(1);
}
return dcc_fix_debug_info(argv[1], argv[2], argv[3]);
}
#endif