Skip to content

Instantly share code, notes, and snippets.

@ikonst
Last active September 28, 2020 12:40
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ikonst/3427195 to your computer and use it in GitHub Desktop.
Save ikonst/3427195 to your computer and use it in GitHub Desktop.
Patches ELF (EABI) ARM attribute Tag_ABI_PCS_wchar_t.
/*
* Patches ELF (EABI) ARM attribute Tag_ABI_PCS_wchar_t.
*
* This utility's chief purpose is to remove the flag indicating sizeof(wchar_t)
* from ARM EABI binaries -- or more precisely, setting it to 0 (= undefined).
*
* It is useful to mark libraries which don't use wchar_t at all as wchar_t-width-agnostic.
* This way, they can be linked to both sizeof(wchar_t)=2 and sizeof(wchar_t)=4 programs
* without any warnings.
*
* Note: gcc always sets this tag on binaries, even when they don't make any use of wchar_t,
* and this is why this utility is useful. This way, you don't need to recompile a library,
* as long as you guarantee it makes no use of wchar_t.
*/
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <elf.h>
#if !defined(SHT_ARM_ATTRIBUTES)
#define SHT_ARM_ATTRIBUTES 0x70000003 /* Section holds attributes. */
#endif
// Based on DWARF _decode_uleb128
int parse_uleb128(int fd, unsigned long int* result, off_t* pos, size_t size)
{
int shift = 0;
unsigned char byte;
*result = 0;
while (*pos < size)
{
if (read(fd, &byte, sizeof(byte)) != 1)
{
perror("reading ULEB128");
return 1;
}
++(*pos);
*result |= (byte & 0x7f) << shift;
if ((byte & 0x80) == 0)
break;
if (*pos >= size)
{
printf("Error: Unterminated ULEB128.\n");
return 1;
}
shift += 7;
}
return 0;
}
int parse_ntbs(int fd, char* result, size_t result_size, off_t* pos, size_t size)
{
off_t i = 0;
while (*pos < size)
{
char byte;
if (read(fd, &byte, sizeof(byte)) != 1)
{
perror("skipping NTBS\n");
return 1;
}
++(*pos);
if ((result != NULL) && (i < result_size))
{
result[i] = byte;
}
++i;
if (byte == 0)
break;
if (*pos >= size)
{
printf("Error: Unterminated NTBS.\n");
return 1;
}
}
// ensure null-termination
if (result != NULL)
{
result[result_size-1] = '\0';
}
return 0;
}
int parse_eabi_attr_aeabi_subsection(int fd, off_t* pos, size_t sh_size, char wchar_size)
{
unsigned long int attr, value;
int ret;
char buf[1024];
if (sh_size < 1)
{
printf("Error: aeabi subsection too small.\n");
return 1;
}
unsigned char tag;
if (read(fd, &tag, sizeof(tag)) != sizeof(tag))
{
perror("reading aeabi subsection tag\n");
return 1;
}
// if tag = section or tag = symbol, skip over section/symbol identifiers
if (tag == 2 || tag == 3)
{
unsigned long int id;
do
{
ret = parse_uleb128(fd, &id, pos, sh_size);
if (ret != 0)
return ret;
} while (id != 0);
}
while (*pos < sh_size)
{
ret = parse_uleb128(fd, &attr, pos, sh_size);
if (ret != 0)
return ret;
switch (attr)
{
case 4: // Tag_CPU_raw_name
case 5: // Tag_CPU_name
case 67: // Tag_conformance
case 32: // Tag_compatibility
ret = parse_ntbs(fd, buf, sizeof(buf), pos, sh_size);
if (ret != 0)
return ret;
break;
case 18: // Tag_ABI_PCS_wchar_t
ret = parse_uleb128(fd, &value, pos, sh_size);
if (ret != 0)
return ret;
printf("Tag_ABI_PCS_wchar_t = %ld\n", value);
if (wchar_size >= 0)
{
if (value > 0x7f)
{
// This utility does not support resizing structures
printf("Error: Unable to patch Tag_ABI_PCS_wchar_t: old value is too big.\n");
}
if (lseek(fd, -1, SEEK_CUR) == (off_t)-1)
{
perror("seeking to patch");
return 1;
}
if (write(fd, &wchar_size, sizeof(wchar_size)) != sizeof(wchar_size))
{
perror("patching");
return 1;
}
printf("\tPatched to %d\n", wchar_size);
}
break;
default:
// skip over tag -- for >32, we follow ARM's convention
if (attr > 32)
{
if ((attr % 2) == 0) // even
ret = parse_uleb128(fd, &value, pos, sh_size);
else // odd
ret = parse_ntbs(fd, NULL, 0, pos, sh_size);
}
else
{
// all NTBS tags are in the switch above; the rest are ULEB128
ret = parse_uleb128(fd, &value, pos, sh_size);
}
if (ret != 0)
return ret;
break;
}
}
return 0;
}
int parse_eabi_attr_section(int fd, size_t sh_size, char wchar_size)
{
int ret;
if (sh_size < 1)
{
printf("Error: Empty ARM attributes section.\n");
}
char version;
if (read(fd, &version, sizeof(version)) != sizeof(version))
{
perror("reading version");
return 1;
}
if (version != 'A')
{
printf("Error: Unknown ARM attribute section format version '%c'.\n", version);
return 1;
}
off_t pos = 1;
while (pos < sh_size)
{
Elf32_Word subsect_size;
if (pos + sizeof(subsect_size) > sh_size)
{
printf("Error: Unexpected end of ARM attribute section\n");
return 1;
}
if (read(fd, &subsect_size, sizeof(subsect_size)) != sizeof(subsect_size))
{
perror("reading subsection size");
return 1;
}
if (pos + subsect_size > sh_size)
{
printf("Error: ARM attribute subsection outside of section bounds.\n");
return 1;
}
off_t spos = sizeof(subsect_size); // positon within subsection
char vendor_name[128];
ret = parse_ntbs(fd, vendor_name, sizeof(vendor_name), &spos, subsect_size);
if (ret != 0)
return ret;
if (strcmp(vendor_name, "aeabi") == 0)
{
ret = parse_eabi_attr_aeabi_subsection(fd, &spos, subsect_size, wchar_size);
if (ret != 0)
return ret;
}
else
{
if (lseek(fd, subsect_size - spos, SEEK_CUR) != -1)
{
perror("skipping over unknown subsection");
return 1;
}
spos = subsect_size;
}
pos += spos;
}
return 0;
}
int parse(int fd, char wchar_size)
{
Elf32_Ehdr ehdr;
if (read(fd, &ehdr, sizeof(ehdr)) != sizeof(ehdr))
{
perror("reading ELf32_Ehdr");
return 1;
}
if (memcmp(ehdr.e_ident, ELFMAG, 4) != 0)
{
printf("Error: Invalid ELF magic.");
return 1;
}
// Real-world ARM EABI files don't have this, for some reason
#ifdef IDENT_HAS_EABI
if (ehdr.e_ident[EI_OSABI] != 64)
{
printf("Error: Not ARM EABI file.\n");
return 1;
}
#endif
if (ehdr.e_machine != EM_ARM)
{
printf("Error: Not an ARM ELF file.\n");
return 1;
}
if (ehdr.e_shoff == 0)
{
printf("Error: ELF file has no section table.\n");
return 1;
}
if (ehdr.e_shentsize != sizeof(Elf32_Shdr))
{
printf("Error: Section header entry size %d doesn't match sizeof(ELf32_Shdr)=%d.\n", ehdr.e_shentsize, sizeof(Elf32_Shdr));
return 1;
}
if (lseek(fd, ehdr.e_shoff, SEEK_SET) == (off_t)-1)
{
perror("seeking to section header table");
return 1;
}
for (int i=0; i<ehdr.e_shnum; ++i)
{
Elf32_Shdr shdr;
if (read(fd, &shdr, sizeof(shdr)) != sizeof(shdr))
{
perror("reading Elf32_Shdr");
return 1;
}
if (shdr.sh_type != SHT_ARM_ATTRIBUTES)
continue;
off_t current_pos = lseek(fd, 0, SEEK_CUR);
if (lseek(fd, shdr.sh_offset, SEEK_SET) == (off_t)-1)
{
perror("seeking to attributes section");
return 1;
}
int ret = parse_eabi_attr_section(fd, shdr.sh_size, wchar_size);
if (ret != 0)
return ret;
if (lseek(fd, current_pos, SEEK_SET) == (off_t)-1)
{
perror("restoring position");
return 1;
}
}
return 0;
}
int process(const char* filename, int wchar_size)
{
int fd = open(filename, O_RDWR);
if (fd == -1)
{
perror("opening file");
return 1;
}
int ret = parse(fd, wchar_size);
close(fd);
return ret;
}
int main(int argc, const char** argv)
{
if (argc < 2 || argc > 3)
{
printf("Syntax: eabi-wchar [filename] [Tag_ABI_PCS_wchar_t]\n");
return 1;
}
int wchar_size;
if (argc == 3)
{
if (sscanf(argv[2], "%d", &wchar_size) != 1)
{
printf("Invalid Tag_ABI_PCS_wchar_t value %s.\n", argv[2]);
return 1;
}
if (wchar_size > 0x7f)
{
printf("Error: We do not support patching with TAG_ABI_PCS_wchar_t %d greater than 0x7f.\n", wchar_size);
return 1;
}
}
else
{
wchar_size = -1;
}
return process(argv[1], (char)wchar_size);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment