#include <string.h>
#include <mach/vm_prot.h>
#include <mach-o/loader.h>
#include <sys/types.h>
#if KERNEL
#include <mach/vm_param.h>
#else
#include <mach/mach_init.h>
#endif
#define DEBUG_ASSERT_COMPONENT_NAME_STRING "kxld"
#include <AssertMacros.h>
#include "kxld_reloc.h"
#include "kxld_sect.h"
#include "kxld_seg.h"
#include "kxld_symtab.h"
#include "kxld_util.h"
#define MAX_SEGS 20
#define TEXT_SEG_PROT (VM_PROT_READ | VM_PROT_EXECUTE)
#define DATA_SEG_PROT (VM_PROT_READ | VM_PROT_WRITE)
#if KXLD_USER_OR_OBJECT
static kern_return_t reorder_sections(KXLDSeg *seg, KXLDArray *section_order);
static void reorder_section(KXLDArray *sects, u_int *sect_reorder_index,
KXLDSect **reorder_buffer, u_int reorder_buffer_index);
#endif
#if 0
static KXLDSeg * get_segment_by_name(KXLDArray *segarray, const char *name);
#endif
#if KXLD_USER_OR_ILP32
static kern_return_t seg_export_macho_header_32(const KXLDSeg *seg, u_char *buf,
u_long *header_offset, u_long header_size, u_long data_offset);
#endif
#if KXLD_USER_OR_LP64
static kern_return_t seg_export_macho_header_64(const KXLDSeg *seg, u_char *buf,
u_long *header_offset, u_long header_size, u_long data_offset);
#endif
static KXLDSect * get_sect_by_index(const KXLDSeg *seg, u_int idx);
#if KXLD_USER_OR_ILP32
kern_return_t
kxld_seg_init_from_macho_32(KXLDSeg *seg, struct segment_command *src)
{
kern_return_t rval = KERN_FAILURE;
check(seg);
check(src);
strlcpy(seg->segname, src->segname, sizeof(seg->segname));
seg->base_addr = src->vmaddr;
seg->link_addr = src->vmaddr;
seg->vmsize = src->vmsize;
seg->fileoff = src->fileoff;
seg->maxprot = src->maxprot;
seg->initprot = src->initprot;
seg->flags = src->flags;
rval = kxld_array_init(&seg->sects, sizeof(KXLDSect *), src->nsects);
require_noerr(rval, finish);
rval = KERN_SUCCESS;
finish:
return rval;
}
#endif
#if KXLD_USER_OR_LP64
kern_return_t
kxld_seg_init_from_macho_64(KXLDSeg *seg, struct segment_command_64 *src)
{
kern_return_t rval = KERN_FAILURE;
check(seg);
check(src);
strlcpy(seg->segname, src->segname, sizeof(seg->segname));
seg->base_addr = src->vmaddr;
seg->link_addr = src->vmaddr;
seg->vmsize = src->vmsize;
seg->fileoff = src->fileoff;
seg->maxprot = src->maxprot;
seg->initprot = src->initprot;
seg->flags = src->flags;
rval = kxld_array_init(&seg->sects, sizeof(KXLDSect *), src->nsects);
require_noerr(rval, finish);
rval = KERN_SUCCESS;
finish:
return rval;
}
#endif
#if KXLD_USER_OR_OBJECT
kern_return_t
kxld_seg_create_seg_from_sections(KXLDArray *segarray, KXLDArray *sectarray)
{
kern_return_t rval = KERN_FAILURE;
KXLDSeg *seg = NULL;
KXLDSect *sect = NULL;
KXLDSect **sectp = NULL;
u_int i = 0;
rval = kxld_array_init(segarray, sizeof(KXLDSeg), 1);
require_noerr(rval, finish);
seg = kxld_array_get_item(segarray, 0);
seg->initprot = VM_PROT_ALL;
seg->maxprot = VM_PROT_ALL;
seg->link_addr = 0;
rval = kxld_array_init(&seg->sects, sizeof(KXLDSect *), sectarray->nitems);
require_noerr(rval, finish);
for (i = 0; i < sectarray->nitems; ++i) {
sect = kxld_array_get_item(sectarray, i);
sectp = kxld_array_get_item(&seg->sects, i);
*sectp = sect;
}
rval = KERN_SUCCESS;
finish:
return rval;
}
kern_return_t
kxld_seg_finalize_object_segment(KXLDArray *segarray, KXLDArray *section_order,
u_long hdrsize)
{
kern_return_t rval = KERN_FAILURE;
KXLDSeg *seg = NULL;
KXLDSect *sect = NULL;
u_long sect_offset = 0;
u_int i = 0;
check(segarray);
check(section_order);
require_action(segarray->nitems == 1, finish, rval=KERN_FAILURE);
seg = kxld_array_get_item(segarray, 0);
rval = reorder_sections(seg, section_order);
require_noerr(rval, finish);
seg->link_addr = round_page(hdrsize);
sect_offset = (u_long) seg->link_addr;
for (i = 0; i < seg->sects.nitems; ++i) {
sect = *(KXLDSect **)kxld_array_get_item(&seg->sects, i);
sect->link_addr = kxld_sect_align_address(sect, sect_offset);
sect_offset = (u_long) (sect->link_addr + sect->size);
}
seg->vmsize = round_page(sect_offset) - seg->link_addr;
rval = KERN_SUCCESS;
finish:
return rval;
}
static kern_return_t
reorder_sections(KXLDSeg *seg, KXLDArray *section_order)
{
kern_return_t rval = KERN_FAILURE;
KXLDSect *sect = NULL;
KXLDSect **reorder_buffer = NULL;
KXLDSectionName *section_name = NULL;
const char *segname = NULL;
u_int sect_index = 0, legacy_index = 0, sect_reorder_index = 0;
u_int i = 0, j = 0;
u_int sect_start = 0, sect_end = 0, legacy_start = 0, legacy_end = 0;
u_int nsects = 0;
check(seg);
check(section_order);
reorder_buffer = kxld_alloc(
seg->sects.nitems * sizeof(*reorder_buffer));
require_action(reorder_buffer, finish, rval=KERN_RESOURCE_SHORTAGE);
while (legacy_index < section_order->nitems) {
legacy_start = legacy_index++;
legacy_end = legacy_index;
section_name = kxld_array_get_item(section_order, legacy_start);
segname = section_name->segname;
while (legacy_index < section_order->nitems) {
section_name = kxld_array_get_item(section_order, legacy_index);
if (!streq_safe(segname, section_name->segname,
sizeof(section_name->segname)))
{
break;
}
++legacy_index;
++legacy_end;
}
sect_start = sect_index;
sect_end = sect_index;
while (sect_index < seg->sects.nitems) {
sect = *(KXLDSect **) kxld_array_get_item(&seg->sects, sect_index);
if (!streq_safe(segname, sect->segname, sizeof(sect->segname))) {
break;
}
++sect_index;
++sect_end;
}
nsects = sect_end - sect_start;
if (!nsects) continue;
for (i = sect_start; i < sect_end; ++i) {
reorder_buffer[i - sect_start] =
*(KXLDSect **) kxld_array_get_item(&seg->sects, i);
}
sect_reorder_index = sect_start;
for (i = legacy_start; i < legacy_end; ++i) {
section_name = kxld_array_get_item(section_order, i);
sect = NULL;
for (j = 0; j < nsects; ++j) {
sect = reorder_buffer[j];
if (!sect) continue;
if (streq_safe(section_name->sectname, sect->sectname,
sizeof(section_name->sectname)))
{
break;
}
sect = NULL;
}
if (sect) {
(void) reorder_section(&seg->sects, §_reorder_index,
reorder_buffer, j);
}
}
for (i = 0; i < nsects; ++i) {
if (!reorder_buffer[i]) continue;
reorder_section(&seg->sects, §_reorder_index, reorder_buffer, i);
}
}
rval = KERN_SUCCESS;
finish:
if (reorder_buffer) {
kxld_free(reorder_buffer, seg->sects.nitems * sizeof(*reorder_buffer));
reorder_buffer = NULL;
}
return rval;
}
static void
reorder_section(KXLDArray *sects, u_int *sect_reorder_index,
KXLDSect **reorder_buffer, u_int reorder_buffer_index)
{
KXLDSect **tmp = NULL;
tmp = kxld_array_get_item(sects, *sect_reorder_index);
*tmp = reorder_buffer[reorder_buffer_index];
reorder_buffer[reorder_buffer_index]->sectnum = *sect_reorder_index;
reorder_buffer[reorder_buffer_index] = NULL;
++(*sect_reorder_index);
}
kern_return_t
kxld_seg_init_linkedit(KXLDArray *segs)
{
kern_return_t rval = KERN_FAILURE;
KXLDSeg *seg = NULL;
KXLDSeg *le = NULL;
rval = kxld_array_resize(segs, 2);
require_noerr(rval, finish);
seg = kxld_array_get_item(segs, 0);
le = kxld_array_get_item(segs, 1);
strlcpy(le->segname, SEG_LINKEDIT, sizeof(le->segname));
le->link_addr = round_page(seg->link_addr + seg->vmsize);
le->maxprot = VM_PROT_ALL;
le->initprot = VM_PROT_DEFAULT;
rval = KERN_SUCCESS;
finish:
return rval;
}
#endif
void
kxld_seg_clear(KXLDSeg *seg)
{
check(seg);
bzero(seg->segname, sizeof(seg->segname));
seg->base_addr = 0;
seg->link_addr = 0;
seg->vmsize = 0;
seg->flags = 0;
seg->maxprot = 0;
seg->initprot = 0;
kxld_array_clear(&seg->sects);
}
void
kxld_seg_deinit(KXLDSeg *seg)
{
check(seg);
kxld_array_deinit(&seg->sects);
bzero(seg, sizeof(*seg));
}
kxld_size_t
kxld_seg_get_vmsize(const KXLDSeg *seg)
{
check(seg);
return seg->vmsize;
}
u_long
kxld_seg_get_macho_header_size(const KXLDSeg *seg, boolean_t is_32_bit)
{
u_long size = 0;
check(seg);
if (is_32_bit) {
size += sizeof(struct segment_command);
} else {
size += sizeof(struct segment_command_64);
}
size += seg->sects.nitems * kxld_sect_get_macho_header_size(is_32_bit);
return size;
}
#if 0
u_long
kxld_seg_get_macho_data_size(const KXLDSeg *seg)
{
u_long size = 0;
u_int i = 0;
KXLDSect *sect = NULL;
check(seg);
for (i = 0; i < seg->sects.nitems; ++i) {
sect = get_sect_by_index(seg, i);
size = (u_long) kxld_sect_align_address(sect, size);
size += kxld_sect_get_macho_data_size(sect);
}
return round_page(size);
}
#endif
static KXLDSect *
get_sect_by_index(const KXLDSeg *seg, u_int idx)
{
check(seg);
return *(KXLDSect **) kxld_array_get_item(&seg->sects, idx);
}
kern_return_t
kxld_seg_export_macho_to_file_buffer(const KXLDSeg *seg, u_char *buf,
u_long *header_offset, u_long header_size,
u_long *data_offset, u_long data_size,
boolean_t is_32_bit)
{
kern_return_t rval = KERN_FAILURE;
KXLDSect *sect = NULL;
u_long base_data_offset = *data_offset;
u_int i = 0;
struct segment_command *hdr32 =
(struct segment_command *) ((void *) (buf + *header_offset));
struct segment_command_64 *hdr64 =
(struct segment_command_64 *) ((void *) (buf + *header_offset));
check(seg);
check(buf);
check(header_offset);
check(data_offset);
KXLD_3264_FUNC(is_32_bit, rval,
seg_export_macho_header_32, seg_export_macho_header_64,
seg, buf, header_offset, header_size, *data_offset);
require_noerr(rval, finish);
for (i = 0; i < seg->sects.nitems; ++i) {
sect = get_sect_by_index(seg, i);
rval = kxld_sect_export_macho_to_file_buffer(sect, buf, header_offset,
header_size, data_offset, data_size, is_32_bit);
require_noerr(rval, finish);
}
if (is_32_bit) {
hdr32->filesize = (uint32_t) (*data_offset - base_data_offset);
} else {
hdr64->filesize = (uint64_t) (*data_offset - base_data_offset);
}
*data_offset = round_page(*data_offset);
rval = KERN_SUCCESS;
finish:
return rval;
}
kern_return_t
kxld_seg_export_macho_to_vm(const KXLDSeg *seg, u_char *buf,
u_long *header_offset, u_long header_size,
u_long data_size, kxld_addr_t file_link_addr,
boolean_t is_32_bit)
{
kern_return_t rval = KERN_FAILURE;
KXLDSect *sect = NULL;
u_long data_offset = (u_long) (seg->link_addr - file_link_addr);
u_int i = 0;
check(seg);
check(buf);
check(header_offset);
KXLD_3264_FUNC(is_32_bit, rval,
seg_export_macho_header_32, seg_export_macho_header_64,
seg, buf, header_offset, header_size, data_offset);
require_noerr(rval, finish);
for (i = 0; i < seg->sects.nitems; ++i) {
sect = get_sect_by_index(seg, i);
rval = kxld_sect_export_macho_to_vm(sect, buf, header_offset,
header_size, file_link_addr, data_size, is_32_bit);
require_noerr(rval, finish);
}
rval = KERN_SUCCESS;
finish:
return rval;
}
#if KXLD_USER_OR_ILP32
static kern_return_t
seg_export_macho_header_32(const KXLDSeg *seg, u_char *buf,
u_long *header_offset, u_long header_size, u_long data_offset)
{
kern_return_t rval = KERN_FAILURE;
struct segment_command *seghdr = NULL;
check(seg);
check(buf);
check(header_offset);
require_action(sizeof(*seghdr) <= header_size - *header_offset, finish,
rval=KERN_FAILURE);
seghdr = (struct segment_command *) ((void *) (buf + *header_offset));
*header_offset += sizeof(*seghdr);
seghdr->cmd = LC_SEGMENT;
seghdr->cmdsize = (uint32_t) sizeof(*seghdr);
seghdr->cmdsize +=
(uint32_t) (seg->sects.nitems * kxld_sect_get_macho_header_size(TRUE));
strlcpy(seghdr->segname, seg->segname, sizeof(seghdr->segname));
seghdr->vmaddr = (uint32_t) seg->link_addr;
seghdr->vmsize = (uint32_t) seg->vmsize;
seghdr->fileoff = (uint32_t) data_offset;
seghdr->filesize = (uint32_t) seg->vmsize;
seghdr->maxprot = seg->maxprot;
seghdr->initprot = seg->initprot;
seghdr->nsects = seg->sects.nitems;
seghdr->flags = 0;
rval = KERN_SUCCESS;
finish:
return rval;
}
#endif
#if KXLD_USER_OR_LP64
static kern_return_t
seg_export_macho_header_64(const KXLDSeg *seg, u_char *buf,
u_long *header_offset, u_long header_size, u_long data_offset)
{
kern_return_t rval = KERN_FAILURE;
struct segment_command_64 *seghdr = NULL;
check(seg);
check(buf);
check(header_offset);
require_action(sizeof(*seghdr) <= header_size - *header_offset, finish,
rval=KERN_FAILURE);
seghdr = (struct segment_command_64 *) ((void *) (buf + *header_offset));
*header_offset += sizeof(*seghdr);
seghdr->cmd = LC_SEGMENT_64;
seghdr->cmdsize = (uint32_t) sizeof(*seghdr);
seghdr->cmdsize +=
(uint32_t) (seg->sects.nitems * kxld_sect_get_macho_header_size(FALSE));
strlcpy(seghdr->segname, seg->segname, sizeof(seghdr->segname));
seghdr->vmaddr = (uint64_t) seg->link_addr;
seghdr->vmsize = (uint64_t) seg->vmsize;
seghdr->fileoff = (uint64_t) data_offset;
seghdr->filesize = (uint64_t) seg->vmsize;
seghdr->maxprot = seg->maxprot;
seghdr->initprot = seg->initprot;
seghdr->nsects = seg->sects.nitems;
seghdr->flags = 0;
rval = KERN_SUCCESS;
finish:
return rval;
}
#endif
kern_return_t
kxld_seg_add_section(KXLDSeg *seg, KXLDSect *sect)
{
kern_return_t rval = KERN_FAILURE;
KXLDSect **sectp = NULL;
u_int i;
check(seg);
check(sect);
require_action(streq_safe(seg->segname, sect->segname, sizeof(seg->segname)),
finish, rval=KERN_FAILURE);
for (i = 0; i < seg->sects.nitems; ++i) {
sectp = kxld_array_get_item(&seg->sects, i);
if (NULL == *sectp) {
*sectp = sect;
break;
}
}
require_action(i < seg->sects.nitems, finish, rval=KERN_FAILURE);
rval = KERN_SUCCESS;
finish:
return rval;
}
kern_return_t
kxld_seg_finish_init(KXLDSeg *seg)
{
kern_return_t rval = KERN_FAILURE;
u_int i = 0;
KXLDSect *sect = NULL;
kxld_addr_t maxaddr = 0;
kxld_size_t maxsize = 0;
if (seg->sects.nitems) {
for (i = 0; i < seg->sects.nitems; ++i) {
sect = get_sect_by_index(seg, i);
require_action(sect, finish, rval=KERN_FAILURE);
if (sect->base_addr > maxaddr) {
maxaddr = sect->base_addr;
maxsize = sect->size;
}
}
seg->vmsize = round_page(maxaddr + maxsize - seg->base_addr);
}
rval = KERN_SUCCESS;
finish:
return rval;
}
void
kxld_seg_set_vm_protections(KXLDSeg *seg, boolean_t strict_protections)
{
if (!seg) return;
if (strict_protections) {
if (streq_safe(seg->segname, SEG_TEXT, const_strlen(SEG_TEXT))) {
seg->initprot = TEXT_SEG_PROT;
seg->maxprot = TEXT_SEG_PROT;
} else {
seg->initprot = DATA_SEG_PROT;
seg->maxprot = DATA_SEG_PROT;
}
} else {
seg->initprot = VM_PROT_ALL;
seg->maxprot = VM_PROT_ALL;
}
}
void
kxld_seg_relocate(KXLDSeg *seg, kxld_addr_t link_addr)
{
KXLDSect *sect = NULL;
u_int i = 0;
seg->link_addr += link_addr;
for (i = 0; i < seg->sects.nitems; ++i) {
sect = get_sect_by_index(seg, i);
kxld_sect_relocate(sect, link_addr);
}
}
void
kxld_seg_populate_linkedit(KXLDSeg *seg, const KXLDSymtab *symtab, boolean_t is_32_bit
#if KXLD_PIC_KEXTS
, const KXLDArray *locrelocs
, const KXLDArray *extrelocs
, boolean_t target_supports_slideable_kexts
#endif
)
{
u_long size = 0;
size += kxld_symtab_get_macho_data_size(symtab, is_32_bit);
#if KXLD_PIC_KEXTS
if (target_supports_slideable_kexts) {
size += kxld_reloc_get_macho_data_size(locrelocs, extrelocs);
}
#endif
seg->vmsize = round_page(size);
}