/*
* libhugetlbfs - Easy use of Linux hugepages
* Copyright (C) 2008 Adam Litke, IBM Corporation.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <elf.h>
#include <link.h>
#include <getopt.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/mman.h>
/*
* Eventually we plan to use the libhugetlbfs reporting facility,
* but until that is possible, redefine a simpler version here.
*/
#define REPORT(level, prefix, format, ...) \
do { \
fprintf(stderr, "hugeedit: " prefix ": " format, \
##__VA_ARGS__); \
} while (0)
#include "libhugetlbfs_internal.h"
/*
* All MAP_* options are tagged with MAP_BASE to differentiate them as options
* in the options parser. This must be removed before they are compared.
*/
#define MAP_BASE 0x1000
#define MAP_DISABLE 0x0001
#define MAP_TEXT 0x0002
#define MAP_DATA 0x0004
#define PF_LINUX_HUGETLB 0x100000
extern int optind;
extern char *optarg;
#define OPTION(opts, text) fprintf(stderr, " %-25s %s\n", opts, text)
#define CONT(text) fprintf(stderr, " %-25s %s\n", "", text)
void print_usage()
{
fprintf(stderr, "hugeedit [options] target\n");
fprintf(stderr, "options:\n");
OPTION("--text", "Remap program text into huge pages by default");
OPTION("--data", "Remap program data into huge pages by default");
OPTION("--disable", "Remap no segments into huge pages by default");
OPTION("--help, -h", "Print this usage information");
}
int check_elf_wordsize(void *ehdr)
{
char *e_ident = (char *) ehdr;
if (strncmp(e_ident, ELFMAG, SELFMAG)) {
ERROR("Not a valid ELF executable\n");
exit(EXIT_FAILURE);
}
switch (e_ident[EI_CLASS]) {
case ELFCLASS32:
case ELFCLASS64:
return e_ident[EI_CLASS];
default:
ERROR("Can not determine word size\n");
exit(EXIT_FAILURE);
}
}
/*
* We need to map enough of the binary so that we can access the ELF header and
* all of the program headers. This function takes a pointer to the first page
* of ELF headers which is guaranteed to be enough data to determine if we need
* to map more of the binary. Use mremap to enlarge the mapping if needed.
*
* void **elf - may be updated with a new address if mremap moved it
* unsigned long *size - may be updated with the new mapping size
*/
#define elf_ph_end_offset(e) ((e)->e_phoff + (e)->e_phentsize * (e)->e_phnum)
void check_remap_elf(void **elf, unsigned long *size, int wordsize)
{
unsigned long newsize;
int pagesize = getpagesize();
if (wordsize == ELFCLASS32) {
Elf32_Ehdr *ehdr = *(Elf32_Ehdr **) elf;
newsize = elf_ph_end_offset(ehdr);
} else {
Elf64_Ehdr *ehdr = *(Elf64_Ehdr **) elf;
newsize = elf_ph_end_offset(ehdr);
}
newsize = ALIGN_UP(newsize, pagesize);
if (newsize > *size) {
*size = newsize;
*elf = mremap(*elf, *size, newsize, MREMAP_MAYMOVE);
if (*elf == MAP_FAILED) {
ERROR("Remapping failed: %s\n", strerror(errno));
exit(EXIT_FAILURE);
}
}
}
#define is_text(p) ((((p)->p_flags & (PF_R|PF_W|PF_X)) == (PF_R|PF_X)) && \
((p)->p_memsz == (p)->p_filesz))
#if defined(__powerpc__) && !defined(__powerpc64__)
#define is_data(p) (((p)->p_flags & (PF_R|PF_W|PF_X)) == (PF_R|PF_W|PF_X))
#else
#define is_data(p) (((p)->p_flags & (PF_R|PF_W|PF_X)) == (PF_R|PF_W))
#endif
#define update_phdrs(_BITS_) \
void update_phdrs##_BITS_(Elf##_BITS_##_Ehdr *ehdr, int remap_opts) \
{ \
int i; \
Elf##_BITS_##_Phdr *phdr; \
unsigned long long start, end; \
\
phdr = (Elf##_BITS_##_Phdr *)((char *)ehdr + ehdr->e_phoff); \
for (i = 0; i < ehdr->e_phnum; i++) { \
if (phdr[i].p_type != PT_LOAD) \
continue; \
if (remap_opts) \
phdr[i].p_flags &= ~PF_LINUX_HUGETLB; \
if ((remap_opts & MAP_TEXT) && is_text(&phdr[i])) \
phdr[i].p_flags |= PF_LINUX_HUGETLB; \
if ((remap_opts & MAP_DATA) && is_data(&phdr[i])) \
phdr[i].p_flags |= PF_LINUX_HUGETLB; \
start = (unsigned long long) phdr[i].p_vaddr; \
end = start + phdr[i].p_memsz; \
printf("Segment %i 0x%llx - 0x%llx (%s%s) default is " \
"%s pages\n", i, start, end, \
is_text(&phdr[i]) ? "TEXT" : "", \
is_data(&phdr[i]) ? "DATA" : "", \
(phdr[i].p_flags & PF_LINUX_HUGETLB) ? \
"HUGE" : "BASE"); \
} \
}
update_phdrs(32)
update_phdrs(64)
int main(int argc, char ** argv)
{
char opts[] = "+h";
struct option long_opts[] = {
{"help", no_argument, NULL, 'h'},
{"disable", no_argument, NULL, MAP_BASE|MAP_DISABLE},
{"text", no_argument, NULL, MAP_BASE|MAP_TEXT},
{"data", no_argument, NULL, MAP_BASE|MAP_DATA},
{0},
};
int ret = 0, index = 0, remap_opts = 0;
int fd;
const char *target;
void *ehdr;
unsigned long mapsize = getpagesize();
int target_wordsize;
while (ret != -1) {
ret = getopt_long(argc, argv, opts, long_opts, &index);
if (ret > 0 && (ret & MAP_BASE)) {
remap_opts |= ret;
continue;
}
switch (ret) {
case '?':
print_usage();
exit(EXIT_FAILURE);
case 'h':
print_usage();
exit(EXIT_SUCCESS);
default:
ret = -1;
break;
}
}
index = optind;
remap_opts &= ~MAP_BASE;
if (remap_opts & MAP_DISABLE && remap_opts != MAP_DISABLE) {
ERROR("--disable is not compatible with --text or --data\n");
exit(EXIT_FAILURE);
}
if ((argc - index) != 1) {
print_usage();
exit(EXIT_FAILURE);
}
target = argv[index];
/* We don't need write access unless we plan to alter the binary */
fd = open(target, (remap_opts ? O_RDWR : O_RDONLY));
if (fd < 0) {
ERROR("Opening %s failed: %s\n", target, strerror(errno));
exit(EXIT_FAILURE);
}
ehdr = mmap(NULL, mapsize, PROT_READ | (remap_opts ? PROT_WRITE : 0),
MAP_SHARED, fd, 0);
if (ehdr == MAP_FAILED) {
ERROR("Mapping %s failed: %s\n", target, strerror(errno));
exit(EXIT_FAILURE);
}
target_wordsize = check_elf_wordsize(ehdr);
check_remap_elf(&ehdr, &mapsize, target_wordsize);
if (target_wordsize == ELFCLASS64)
update_phdrs64((Elf64_Ehdr *) ehdr, remap_opts);
else
update_phdrs32((Elf32_Ehdr *) ehdr, remap_opts);
if (munmap(ehdr, mapsize) != 0) {
ERROR("Unmapping %s failed: %s\n", target, strerror(errno));
exit(EXIT_FAILURE);
}
if (close(fd) != 0) {
ERROR("Final close of %s failed: %s -- possible data loss!\n",
target, strerror(errno));
exit(EXIT_FAILURE);
}
exit(EXIT_SUCCESS);
}