summaryrefslogtreecommitdiff
path: root/gpxe/src/arch/i386/core/aout_loader.c
blob: f85620e97f77a3f8b49a0711dc2d17322c89d5e4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
/* a.out */
struct exec {
	unsigned long      a_midmag;	/* flags<<26 | mid<<16 | magic */
	unsigned long      a_text;	/* text segment size */
	unsigned long      a_data;	/* initialized data size */
	unsigned long      a_bss;	/* uninitialized data size */
	unsigned long      a_syms;	/* symbol table size */
	unsigned long      a_entry;	/* entry point */
	unsigned long      a_trsize;	/* text relocation size */
	unsigned long      a_drsize;	/* data relocation size */
};

struct aout_state {
	struct exec head;
	unsigned long curaddr;
	int segment;			/* current segment number, -1 for none */
	unsigned long loc;		/* start offset of current block */
	unsigned long skip;		/* padding to be skipped to current segment */
	unsigned long toread;		/* remaining data to be read in the segment */
};

static struct aout_state astate;

static sector_t aout_download(unsigned char *data, unsigned int len, int eof);
static inline os_download_t aout_probe(unsigned char *data, unsigned int len)
{
	unsigned long start, mid, end, istart, iend;
	if (len < sizeof(astate.head)) {
		return 0;
	}
	memcpy(&astate.head, data, sizeof(astate.head));
	if ((astate.head.a_midmag & 0xffff) != 0x010BL) {
		return 0;
	}
	
	printf("(a.out");
	aout_freebsd_probe();
	printf(")... ");
	/* Check the aout image */
	start  = astate.head.a_entry;
	mid    = (((start + astate.head.a_text) + 4095) & ~4095) + astate.head.a_data;
	end    = ((mid + 4095) & ~4095) + astate.head.a_bss;
	istart = 4096;
	iend   = istart + (mid - start);
	if (!prep_segment(start, mid, end, istart, iend))
		return dead_download;
	astate.segment = -1;
	astate.loc = 0;
	astate.skip = 0;
	astate.toread = 0;
	return aout_download;
}

static sector_t aout_download(unsigned char *data, unsigned int len, int eof)
{
	unsigned int offset;	/* working offset in the current data block */

	offset = 0;

#ifdef AOUT_LYNX_KDI
	astate.segment++;
	if (astate.segment == 0) {
		astate.curaddr = 0x100000;
		astate.head.a_entry = astate.curaddr + 0x20;
	}
	memcpy(phys_to_virt(astate.curaddr), data, len);
	astate.curaddr += len;
	return 0;
#endif

	do {
		if (astate.segment != -1) {
			if (astate.skip) {
				if (astate.skip >= len - offset) {
					astate.skip -= len - offset;
					break;
				}
				offset += astate.skip;
				astate.skip = 0;
			}

			if (astate.toread) {
				if (astate.toread >= len - offset) {
					memcpy(phys_to_virt(astate.curaddr), data+offset,
						len - offset);
					astate.curaddr += len - offset;
					astate.toread -= len - offset;
					break;
				}
				memcpy(phys_to_virt(astate.curaddr), data+offset, astate.toread);
				offset += astate.toread;
				astate.toread = 0;
			}
		}

		/* Data left, but current segment finished - look for the next
		 * segment.  This is quite simple for a.out files.  */
		astate.segment++;
		switch (astate.segment) {
		case 0:
			/* read text */
			astate.curaddr = astate.head.a_entry;
			astate.skip = 4096;
			astate.toread = astate.head.a_text;
			break;
		case 1:
			/* read data */
			/* skip and curaddr may be wrong, but I couldn't find
			 * examples where this failed.  There is no reasonable
			 * documentation for a.out available.  */
			astate.skip = ((astate.curaddr + 4095) & ~4095) - astate.curaddr;
			astate.curaddr = (astate.curaddr + 4095) & ~4095;
			astate.toread = astate.head.a_data;
			break;
		case 2:
			/* initialize bss and start kernel */
			astate.curaddr = (astate.curaddr + 4095) & ~4095;
			astate.skip = 0;
			astate.toread = 0;
			memset(phys_to_virt(astate.curaddr), '\0', astate.head.a_bss);
			goto aout_startkernel;
		default:
			break;
		}
	} while (offset < len);

	astate.loc += len;

	if (eof) {
		unsigned long entry;

aout_startkernel:
		entry = astate.head.a_entry;
		done(1);

		aout_freebsd_boot();
#ifdef AOUT_LYNX_KDI
		xstart32(entry);
#endif
		printf("unexpected a.out variant\n");
		longjmp(restart_etherboot, -2);
	}
	return 0;
}