/* * Copyright (c) 1997 Richard Earnshaw. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by Richard Earnshaw. * 4. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* Simple utility to find instructions in a binary that cause the SA110 rev<3 bug when doing a load-multiple or ldr which writes the PC. */ /* NO ATTEMPT HAS BEEN MADE TO MAKE THIS WORK IN A CROSS ENVIRONMENT. IN PARTICULAR THE CODE ASSUMES THAT THE ENDIANNESS OF THE HOST MATCHES THAT OF THE CODE BEING FIXED. */ /* This file patches binaries as follows. ldm reg, {reglist, pc} is replaced with b patcharea ... patcharea: ldm reg, {reglist, pc} It also spots, but does not attempt to fix the more complex ldr pc, [pc, reg, lsl #2] @ is normally ls b which could be replaced with str pc, [sp, #-4]! b patcharea ... patcharea: add<~cond> sp, sp, #4 b<~cond> str treg, [sp, #-4]! @ treg != reg ldr treg, [sp, #4] ldr treg, [treg, reg, lsl #2] str treg, [sp, #4] ldmia sp!, {treg, pc} This patch would be more dangerous, since it is possible for the second instruction to be the target of another branch, which would then be incorrect. I don't think this case can cause a failure unless the branch table contains more than 1023 entries (since otherwise the load will take a data abort causing the page to be correctly mapped in). Perhaps a more significant case which is detected but not fixed is an indirect call: mov lr, pc ldr pc, [reg, ...] @ pc not used in address This case could be fixed up by moving the ldr instruction to the patch area (leaving the condition in the branch). */ #include #include #include /* All programs are loaded at this base address. */ #define LOAD_ADDR 0x1000 typedef struct fix_s { struct fix_s *next; unsigned long where; unsigned long reloc; unsigned long inst; unsigned long nextinst; } fix; fix *fixlist = NULL; fix *curfix = NULL; int fixcount = 0; static void usage(void) { fprintf(stderr, "Usage: findbad \n"); } fix *newfix(unsigned long where, unsigned long inst) { fix *new; if ((new = (fix *)(malloc(sizeof(fix)))) == NULL) { fprintf(stderr, "Out of memory during Malloc\n"); exit(1); } new->next = NULL; new->where = where; new->inst = inst; new->reloc = new->nextinst = 0; if (fixlist == NULL) fixlist = new; else curfix->next = new; curfix = new; fixcount++; return new; } int main(int argc, char *argv[]) { FILE *in, *out; struct exec header; unsigned long size, max_size; unsigned long i; unsigned long fixup; union { char buf[4096]; unsigned long a[1024]; } x; if (argc != 3) { usage(); exit(1); } if ((in = fopen(argv[1], "r")) == NULL) { perror(argv[1]); exit(1); } fread(&header, sizeof (header), 1, in); if (N_BADMAG(header) || N_GETMAGIC(header) != ZMAGIC) { fprintf(stderr, "%s: Not a valid executable file\n", argv[1]); exit(1); } /* For some types of Magic, the text segment includes the header in the address calculations. XXX does this affect the length of the segment? */ if (fseek(in, /*N_TXTOFF(header)*/ 0, SEEK_SET) < 0) { fprintf(stderr, "%s: Not a valid executable file\nBad text offset\n", argv[1]); exit(1); } max_size = header.a_text; for (size = 0; size < max_size; size += 4096) { unsigned long inst; fread(x.buf, 1, max_size - size > 4096 ? 4096: max_size - size, in); /* Can't fix up a ldr pc, [pc,...] instruction just by moving it, so we have to be a bit more clever */ if (curfix != NULL && curfix->where + 4 == size /* ldr?? pc, [pc, reg, lsl #2] */ && (curfix->inst & 0x0ffffff0) == 0x079ff100) { curfix->nextinst = x.a[0]; printf(" %08x:\t", size + LOAD_ADDR); #ifdef DISASS disass(x.a[0]); #else printf(" %08x\tldr\tpc, [pc, ...]\n", x.a[0]); #endif } inst = x.a[1023]; if ((inst & 0x0e108000) == 0x08108000) { printf("%08x:\t", size + 0xffc + LOAD_ADDR); #ifdef DISASS disass(inst); #else printf("%08x\tldm\treg, {..., pc}\n", inst); #endif newfix(size + 0xffc, inst); } else if ((inst & 0x0c10f000) == 0x0410f000) { printf("%08x:\t", size + 0xffc + LOAD_ADDR); #ifdef DISASS disass(inst); #else printf("%08x\tldr\tpc, [reg...]\n", inst); #endif newfix(size + 0xffc, inst); } } if (fixcount == 0) { fprintf(stderr, "Nothing needs fixing\n"); exit (0); } /* HACK, This assumes that max_size is a multiple of 4096. */ for (i = 1024; i-- > 0;) if (x.a[i] != 0) break; i++; i = 1024 - i; printf("%d words of zero in last page of text.\n", i); /* We can do better than this. For load-multiple instructions, all we need to do is find an equivalent, non-conditional instruction elsewhere in the code segment. We can then branch to that. We only need patch space for instructions that do not appear elsewhere, and for the ldr pc hackery. */ if (fixcount > i - 1) fprintf(stderr, "Insufficient space for patch instructions (need %d words)\n", fixcount); if ((out = fopen(argv[2], "w")) == NULL) { perror(argv[2]); exit(1); } fseek(in, 0, SEEK_SET); curfix = fixlist; /* Can't use the last word of the page, since that would fail also! */ fixup = max_size - 8; for (size = 0; size < max_size; size += 4096) { fread(x.buf, 1, max_size - size > 4096 ? 4096 : max_size - size, in); /* This page needs fixing. */ if (curfix && (curfix->where & ~0xfff) == size) { if ((curfix->inst & 0x0e108000) == 0x08108000) /* ldm */ { curfix->reloc = fixup; fixup -= 4; if (x.a[1023] != curfix->inst) abort(); x.a[1023] = ((curfix->inst & 0xf0000000) | 0x0a000000 | ((curfix->reloc - (curfix->where + 8)) >> 2)); } else fprintf(stderr, "ldr pc not yet fixed up\n"); curfix = curfix->next; } if (max_size - size > 4096) fwrite(x.buf, 1, 4096, out); } for (curfix = fixlist; curfix != NULL; curfix = curfix->next) { if (curfix->reloc != 0) { int word = (curfix->reloc & 0xfff) >> 2; x.a[word] = 0xe0000000 | (curfix->inst & 0x0fffffff); } } fwrite(x.buf, 1, max_size - (size - 4096), out); while ((size = fread(x.buf, 1, 4096, in)) != 0) fwrite(x.buf, 1, size, out); fclose(in); fclose(out); return 0; }