summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEelco Dolstra <e.dolstra@tudelft.nl>2004-09-23 20:48:21 (GMT)
committerEelco Dolstra <e.dolstra@tudelft.nl>2004-09-23 20:48:21 (GMT)
commitf967dff666cb50c59f8f58b4e0838d50d1e03a0c (patch)
tree5de9b2978f35ec6c971b560ada08cfaf5d8fe926
downloadpatchelf-f967dff666cb50c59f8f58b4e0838d50d1e03a0c.zip
patchelf-f967dff666cb50c59f8f58b4e0838d50d1e03a0c.tar.gz
patchelf-f967dff666cb50c59f8f58b4e0838d50d1e03a0c.tar.bz2
* Proof-of-concept of a tool to patch *existing* ELF executable to use
our own glibc, to change the rpath, and so on. This enable us to use third-party programs in a pure Nix environment, and to "shrink" the rpath of Nix-built executables to contain only those directories that are actually needed (in order to reduce the number of retained dependencies). In order to use our own glibc, we have to set the executable's ELF interpreter field to our glib'c ld-linux.so instead of the typical /lib/ld-linux.so.2. The name of the interpreter is stored in the PT_INTERP segment of the executable. A complication is that this segment is too small to store the path to our ld-linux.so. We cannot just make this segment bigger, since the segment is actually mapped into the executable's virtual memory space, so that making it bigger will move all following segments in memory. That would break executables, since they are typically not position-independant (they expect to be loaded at specific addresses in memory). The solution is to add a *new* segment at the end of the executable. (The data containing the original PT_INTERP segment becomes "dead" space within the executable.) This seems to work: e.g., I've succesfully patched SuSE's /bin/cat. Something similar could be done for the rpath, although that's a bit more complicated since the rpath string is stored indirectly (the PT_DYNAMIC segment merely contains a pointer to a string in the DT_STRTAB section of the executable, which we cannot grow either, so we would have to copy it to the end of the file).
-rw-r--r--Makefile2
-rw-r--r--patchelf.c131
2 files changed, 133 insertions, 0 deletions
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..0075654
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,2 @@
+patchelf: patchelf.c
+ gcc -Wall -o patchelf patchelf.c \ No newline at end of file
diff --git a/patchelf.c b/patchelf.c
new file mode 100644
index 0000000..4ecda2e
--- /dev/null
+++ b/patchelf.c
@@ -0,0 +1,131 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <assert.h>
+#include <string.h>
+#include <errno.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include <elf.h>
+
+
+static off_t fileSize, maxSize = 128 * 1024;
+static unsigned char * contents = 0;
+
+
+static void error(char * msg)
+{
+ if (errno) perror(msg); else printf("%s\n", msg);
+ exit(1);
+}
+
+
+static void growFile(off_t newSize)
+{
+ if (newSize > maxSize) error("maximum file size exceeded");
+ if (newSize <= fileSize) return;
+ if (newSize > fileSize)
+ memset(contents + fileSize, 0, newSize - fileSize);
+ fileSize = newSize;
+}
+
+
+static void readFile(char * fileName)
+{
+ struct stat st;
+ if (stat(fileName, &st) != 0) error("stat");
+ fileSize = st.st_size;
+
+ contents = malloc(fileSize + maxSize);
+ if (!contents) abort();
+
+ int fd = open(fileName, O_RDONLY);
+ if (fd == -1) error("open");
+
+ if (read(fd, contents, fileSize) != fileSize) error("read");
+
+ close(fd);
+}
+
+
+static void writeFile(char * fileName)
+{
+ int fd = open(fileName, O_CREAT | O_TRUNC | O_WRONLY, 0777);
+ if (fd == -1) error("open");
+
+ if (write(fd, contents, fileSize) != fileSize) error("write");
+
+ close(fd);
+}
+
+
+//char newInterpreter[] = "/lib/ld-linux.so.2";
+char newInterpreter[] = "/nix/store/42de22963bca8f234ad54b01118215df-glibc-2.3.2/lib/ld-linux.so.2";
+
+
+static void patchElf(char * fileName)
+{
+ fprintf(stderr, "patching %s\n", fileName);
+
+ readFile(fileName);
+
+ if (fileSize < sizeof(Elf32_Ehdr)) error("missing ELF header");
+
+ Elf32_Ehdr * hdr = (Elf32_Ehdr *) contents;
+
+ if (memcmp(hdr->e_ident, ELFMAG, 4) != 0)
+ error("not an ELF executable");
+
+ if (contents[EI_CLASS] != ELFCLASS32 ||
+ contents[EI_DATA] != ELFDATA2LSB ||
+ contents[EI_VERSION] != EV_CURRENT)
+ error("ELF executable is not 32-bit, little-endian, version 1");
+
+ if (hdr->e_type != ET_EXEC && hdr->e_type != ET_DYN)
+ error("wrong ELF type");
+
+ fprintf(stderr, "%d ph entries, %d sh entries\n",
+ hdr->e_phnum, hdr->e_shnum);
+
+ if (hdr->e_phoff + hdr->e_phnum * hdr->e_phentsize > fileSize)
+ error("missing program header");
+
+ if (hdr->e_shoff + hdr->e_shnum * hdr->e_shentsize > fileSize)
+ error("missing program header");
+
+ /* Find the PT_INTERP segment. */
+ Elf32_Phdr * phdr = (Elf32_Phdr *) (contents + hdr->e_phoff);
+
+ int i;
+ for (i = 0; i < hdr->e_phnum; ++i, ++phdr) {
+ fprintf(stderr, "segment type %d at %x\n", phdr->p_type, phdr->p_offset);
+ if (phdr->p_type == PT_INTERP) {
+ fprintf(stderr, "found interpreter (%s)\n",
+ (char *) (contents + phdr->p_offset));
+ unsigned int segLen = strlen(newInterpreter) + 1;
+ phdr->p_offset = fileSize;
+ growFile(phdr->p_offset + segLen);
+ phdr->p_vaddr = phdr->p_paddr = 0x08048000 + phdr->p_offset;
+ phdr->p_filesz = phdr->p_memsz = segLen;
+ strncpy(contents + phdr->p_offset, newInterpreter, segLen);
+ }
+ }
+
+ writeFile("./new.exe");
+}
+
+
+int main(int argc, char * * argv)
+{
+ if (argc != 2) {
+ fprintf(stderr, "syntax: %s FILENAME\n", argv[0]);
+ return 1;
+ }
+
+ patchElf(argv[1]);
+
+ return 0;
+}