1 | /*****************************************************************************/ |
2 | /* */ |
3 | /* convert.c */ |
4 | /* */ |
5 | /* Actual conversion routines for the co65 object file converter */ |
6 | /* */ |
7 | /* */ |
8 | /* */ |
9 | /* (C) 2003-2011, Ullrich von Bassewitz */ |
10 | /* Roemerstrasse 52 */ |
11 | /* D-70794 Filderstadt */ |
12 | /* EMail: uz@cc65.org */ |
13 | /* */ |
14 | /* */ |
15 | /* This software is provided 'as-is', without any expressed or implied */ |
16 | /* warranty. In no event will the authors be held liable for any damages */ |
17 | /* arising from the use of this software. */ |
18 | /* */ |
19 | /* Permission is granted to anyone to use this software for any purpose, */ |
20 | /* including commercial applications, and to alter it and redistribute it */ |
21 | /* freely, subject to the following restrictions: */ |
22 | /* */ |
23 | /* 1. The origin of this software must not be misrepresented; you must not */ |
24 | /* claim that you wrote the original software. If you use this software */ |
25 | /* in a product, an acknowledgment in the product documentation would be */ |
26 | /* appreciated but is not required. */ |
27 | /* 2. Altered source versions must be plainly marked as such, and must not */ |
28 | /* be misrepresented as being the original software. */ |
29 | /* 3. This notice may not be removed or altered from any source */ |
30 | /* distribution. */ |
31 | /* */ |
32 | /*****************************************************************************/ |
33 | |
34 | |
35 | |
36 | #include <stdio.h> |
37 | #include <string.h> |
38 | #include <errno.h> |
39 | |
40 | /* common */ |
41 | #include "debugflag.h" |
42 | #include "print.h" |
43 | #include "version.h" |
44 | #include "xmalloc.h" |
45 | #include "xsprintf.h" |
46 | |
47 | /* co65 */ |
48 | #include "error.h" |
49 | #include "global.h" |
50 | #include "model.h" |
51 | #include "o65.h" |
52 | #include "convert.h" |
53 | |
54 | |
55 | |
56 | /*****************************************************************************/ |
57 | /* Code */ |
58 | /*****************************************************************************/ |
59 | |
60 | |
61 | |
62 | static void PrintO65Stats (const O65Data* D) |
63 | /* Print information about the O65 file if --verbose is given */ |
64 | { |
65 | Print (stdout, 1, "Size of text segment: %5lu\n" , D->Header.tlen); |
66 | Print (stdout, 1, "Size of data segment: %5lu\n" , D->Header.dlen); |
67 | Print (stdout, 1, "Size of bss segment: %5lu\n" , D->Header.blen); |
68 | Print (stdout, 1, "Size of zeropage segment: %5lu\n" , D->Header.zlen); |
69 | Print (stdout, 1, "Number of imports: %5u\n" , CollCount (&D->Imports)); |
70 | Print (stdout, 1, "Number of exports: %5u\n" , CollCount (&D->Exports)); |
71 | Print (stdout, 1, "Number of text segment relocations: %5u\n" , CollCount (&D->TextReloc)); |
72 | Print (stdout, 1, "Number of data segment relocations: %5u\n" , CollCount (&D->DataReloc)); |
73 | } |
74 | |
75 | |
76 | |
77 | static void SetupSegLabels (FILE* F) |
78 | /* Setup the segment label names */ |
79 | { |
80 | if (BssLabel) { |
81 | fprintf (F, ".export\t\t%s\n" , BssLabel); |
82 | } else { |
83 | BssLabel = xstrdup ("BSS" ); |
84 | } |
85 | if (CodeLabel) { |
86 | fprintf (F, ".export\t\t%s\n" , CodeLabel); |
87 | } else { |
88 | CodeLabel = xstrdup ("CODE" ); |
89 | } |
90 | if (DataLabel) { |
91 | fprintf (F, ".export\t\t%s\n" , DataLabel); |
92 | } else { |
93 | DataLabel = xstrdup ("DATA" ); |
94 | } |
95 | if (ZeropageLabel) { |
96 | fprintf (F, ".export\t\t%s\n" , ZeropageLabel); |
97 | } else { |
98 | ZeropageLabel = xstrdup ("ZEROPAGE" ); |
99 | } |
100 | } |
101 | |
102 | |
103 | |
104 | static const char* LabelPlusOffs (const char* Label, long Offs) |
105 | /* Generate "Label+xxx" in a static buffer and return a pointer to the buffer */ |
106 | { |
107 | static char Buf[256]; |
108 | xsprintf (Buf, sizeof (Buf), "%s%+ld" , Label, Offs); |
109 | return Buf; |
110 | } |
111 | |
112 | |
113 | |
114 | static const char* RelocExpr (const O65Data* D, unsigned char SegID, |
115 | unsigned long Val, const O65Reloc* R) |
116 | /* Generate the segment relative relocation expression. R is only used if the |
117 | ** expression contains am import, and may be NULL if this is an error (which |
118 | ** is then flagged). |
119 | */ |
120 | { |
121 | const O65Import* Import; |
122 | |
123 | switch (SegID) { |
124 | |
125 | case O65_SEGID_UNDEF: |
126 | if (R) { |
127 | if (R->SymIdx >= CollCount (&D->Imports)) { |
128 | Error ("Import index out of range (input file corrupt)" ); |
129 | } |
130 | Import = CollConstAt (&D->Imports, R->SymIdx); |
131 | return LabelPlusOffs (Import->Name, Val); |
132 | } else { |
133 | Error ("Relocation references an import which is not allowed here" ); |
134 | return 0; |
135 | } |
136 | break; |
137 | |
138 | case O65_SEGID_TEXT: |
139 | return LabelPlusOffs (CodeLabel, Val - D->Header.tbase); |
140 | |
141 | case O65_SEGID_DATA: |
142 | return LabelPlusOffs (DataLabel, Val - D->Header.dbase); |
143 | |
144 | case O65_SEGID_BSS: |
145 | return LabelPlusOffs (BssLabel, Val - D->Header.bbase); |
146 | |
147 | case O65_SEGID_ZP: |
148 | return LabelPlusOffs (ZeropageLabel, Val - D->Header.zbase); |
149 | |
150 | case O65_SEGID_ABS: |
151 | return LabelPlusOffs ("" , Val); |
152 | |
153 | default: |
154 | Internal ("Cannot handle this segment reference in reloc entry" ); |
155 | } |
156 | |
157 | /* NOTREACHED */ |
158 | return 0; |
159 | } |
160 | |
161 | |
162 | |
163 | static void ConvertImports (FILE* F, const O65Data* D) |
164 | /* Convert the imports */ |
165 | { |
166 | unsigned I; |
167 | |
168 | if (CollCount (&D->Imports) > 0) { |
169 | for (I = 0; I < CollCount (&D->Imports); ++I) { |
170 | |
171 | /* Get the next import */ |
172 | const O65Import* Import = CollConstAt (&D->Imports, I); |
173 | |
174 | /* Import it by name */ |
175 | fprintf (F, ".import\t%s\n" , Import->Name); |
176 | } |
177 | fprintf (F, "\n" ); |
178 | } |
179 | } |
180 | |
181 | |
182 | |
183 | static void ConvertExports (FILE* F, const O65Data* D) |
184 | /* Convert the exports */ |
185 | { |
186 | unsigned I; |
187 | |
188 | if (CollCount (&D->Exports) > 0) { |
189 | for (I = 0; I < CollCount (&D->Exports); ++I) { |
190 | |
191 | /* Get the next import */ |
192 | const O65Export* Export = CollConstAt (&D->Exports, I); |
193 | |
194 | /* First define it */ |
195 | fprintf (F, "%s = %s\n" , |
196 | Export->Name, |
197 | RelocExpr (D, Export->SegID, Export->Val, 0)); |
198 | |
199 | /* Then export it by name */ |
200 | fprintf (F, ".export\t%s\n" , Export->Name); |
201 | } |
202 | fprintf (F, "\n" ); |
203 | } |
204 | } |
205 | |
206 | |
207 | |
208 | static void ConvertSeg (FILE* F, const O65Data* D, const Collection* Relocs, |
209 | const unsigned char* Data, unsigned long Size) |
210 | /* Convert one segment */ |
211 | { |
212 | const O65Reloc* R; |
213 | unsigned RIdx; |
214 | unsigned long Byte; |
215 | |
216 | /* Get the pointer to the first relocation entry if there are any */ |
217 | R = (CollCount (Relocs) > 0)? CollConstAt (Relocs, 0) : 0; |
218 | |
219 | /* Initialize for the loop */ |
220 | RIdx = 0; |
221 | Byte = 0; |
222 | |
223 | /* Walk over the segment data */ |
224 | while (Byte < Size) { |
225 | |
226 | if (R && R->Offs == Byte) { |
227 | /* We've reached an entry that must be relocated */ |
228 | unsigned long Val; |
229 | switch (R->Type) { |
230 | |
231 | case O65_RTYPE_WORD: |
232 | if (Byte >= Size - 1) { |
233 | Error ("Found WORD relocation, but not enough bytes left" ); |
234 | } else { |
235 | Val = (Data[Byte+1] << 8) + Data[Byte]; |
236 | Byte += 2; |
237 | fprintf (F, "\t.word\t%s\n" , RelocExpr (D, R->SegID, Val, R)); |
238 | } |
239 | break; |
240 | |
241 | case O65_RTYPE_HIGH: |
242 | Val = (Data[Byte++] << 8) + R->Val; |
243 | fprintf (F, "\t.byte\t>(%s)\n" , RelocExpr (D, R->SegID, Val, R)); |
244 | break; |
245 | |
246 | case O65_RTYPE_LOW: |
247 | Val = Data[Byte++]; |
248 | fprintf (F, "\t.byte\t<(%s)\n" , RelocExpr (D, R->SegID, Val, R)); |
249 | break; |
250 | |
251 | case O65_RTYPE_SEGADDR: |
252 | if (Byte >= Size - 2) { |
253 | Error ("Found SEGADDR relocation, but not enough bytes left" ); |
254 | } else { |
255 | Val = (((unsigned long) Data[Byte+2]) << 16) + |
256 | (((unsigned long) Data[Byte+1]) << 8) + |
257 | (((unsigned long) Data[Byte+0]) << 0) + |
258 | R->Val; |
259 | Byte += 3; |
260 | fprintf (F, "\t.faraddr\t%s\n" , RelocExpr (D, R->SegID, Val, R)); |
261 | } |
262 | break; |
263 | |
264 | case O65_RTYPE_SEG: |
265 | /* FALLTHROUGH for now */ |
266 | default: |
267 | Internal ("Cannot handle relocation type %d at %lu" , |
268 | R->Type, Byte); |
269 | } |
270 | |
271 | /* Get the next relocation entry */ |
272 | if (++RIdx < CollCount (Relocs)) { |
273 | R = CollConstAt (Relocs, RIdx); |
274 | } else { |
275 | R = 0; |
276 | } |
277 | |
278 | } else { |
279 | /* Just a constant value */ |
280 | fprintf (F, "\t.byte\t$%02X\n" , Data[Byte++]); |
281 | } |
282 | } |
283 | |
284 | fprintf (F, "\n" ); |
285 | } |
286 | |
287 | |
288 | |
289 | static void ConvertCodeSeg (FILE* F, const O65Data* D) |
290 | /* Do code segment conversion */ |
291 | { |
292 | /* Header */ |
293 | fprintf (F, |
294 | ";\n; CODE SEGMENT\n;\n" |
295 | ".segment\t\"%s\"\n" |
296 | "%s:\n" , |
297 | CodeSeg, |
298 | CodeLabel); |
299 | |
300 | /* Segment data */ |
301 | ConvertSeg (F, D, &D->TextReloc, D->Text, D->Header.tlen); |
302 | } |
303 | |
304 | |
305 | |
306 | static void ConvertDataSeg (FILE* F, const O65Data* D) |
307 | /* Do data segment conversion */ |
308 | { |
309 | /* Header */ |
310 | fprintf (F, |
311 | ";\n; DATA SEGMENT\n;\n" |
312 | ".segment\t\"%s\"\n" |
313 | "%s:\n" , |
314 | DataSeg, |
315 | DataLabel); |
316 | |
317 | /* Segment data */ |
318 | ConvertSeg (F, D, &D->DataReloc, D->Data, D->Header.dlen); |
319 | } |
320 | |
321 | |
322 | |
323 | static void ConvertBssSeg (FILE* F, const O65Data* D) |
324 | /* Do bss segment conversion */ |
325 | { |
326 | /* Header */ |
327 | fprintf (F, |
328 | ";\n; BSS SEGMENT\n;\n" |
329 | ".segment\t\"%s\"\n" |
330 | "%s:\n" , |
331 | BssSeg, |
332 | BssLabel); |
333 | |
334 | /* Segment data */ |
335 | fprintf (F, "\t.res\t%lu\n" , D->Header.blen); |
336 | fprintf (F, "\n" ); |
337 | } |
338 | |
339 | |
340 | |
341 | static void ConvertZeropageSeg (FILE* F, const O65Data* D) |
342 | /* Do zeropage segment conversion */ |
343 | { |
344 | /* Header */ |
345 | fprintf (F, ";\n; ZEROPAGE SEGMENT\n;\n" ); |
346 | |
347 | if (Model == O65_MODEL_CC65_MODULE) { |
348 | /* o65 files of type cc65-module are linked together with a definition |
349 | ** file for the zero page, but the zero page is not allocated in the |
350 | ** module itself, but the locations are mapped to the zp locations of |
351 | ** the main file. |
352 | */ |
353 | fprintf (F, ".import\t__ZP_START__\t\t; Linker generated symbol\n" ); |
354 | fprintf (F, "%s = __ZP_START__\n" , ZeropageLabel); |
355 | } else { |
356 | /* Header */ |
357 | fprintf (F, ".segment\t\"%s\": zeropage\n%s:\n" , ZeropageSeg, ZeropageLabel); |
358 | |
359 | /* Segment data */ |
360 | fprintf (F, "\t.res\t%lu\n" , D->Header.zlen); |
361 | } |
362 | fprintf (F, "\n" ); |
363 | } |
364 | |
365 | |
366 | |
367 | void Convert (const O65Data* D) |
368 | /* Convert the o65 file in D using the given output file. */ |
369 | { |
370 | FILE* F; |
371 | unsigned I; |
372 | char* Author = 0; |
373 | |
374 | /* For now, we do only accept o65 files generated by the ld65 linker which |
375 | ** have a specific format. |
376 | */ |
377 | if (!Debug && D->Header.mode != O65_MODE_CC65) { |
378 | Error ("Cannot convert o65 files of this type" ); |
379 | } |
380 | |
381 | /* Output statistics */ |
382 | PrintO65Stats (D); |
383 | |
384 | /* Walk through the options and print them if verbose mode is enabled. |
385 | ** Check for a os=cc65 option and bail out if we didn't find one (for |
386 | ** now - later we switch to special handling). |
387 | */ |
388 | for (I = 0; I < CollCount (&D->Options); ++I) { |
389 | |
390 | /* Get the next option */ |
391 | const O65Option* O = CollConstAt (&D->Options, I); |
392 | |
393 | /* Check the type of the option */ |
394 | switch (O->Type) { |
395 | |
396 | case O65_OPT_FILENAME: |
397 | Print (stdout, 1, "O65 filename option: '%s'\n" , |
398 | GetO65OptionText (O)); |
399 | break; |
400 | |
401 | case O65_OPT_OS: |
402 | if (O->Len == 2) { |
403 | Warning ("Operating system option without data found" ); |
404 | } else { |
405 | Print (stdout, 1, "O65 operating system option: '%s'\n" , |
406 | GetO65OSName (O->Data[0])); |
407 | switch (O->Data[0]) { |
408 | case O65_OS_CC65_MODULE: |
409 | if (Model != O65_MODEL_NONE && |
410 | Model != O65_MODEL_CC65_MODULE) { |
411 | Warning ("Wrong o65 model for input file specified" ); |
412 | } else { |
413 | Model = O65_MODEL_CC65_MODULE; |
414 | } |
415 | break; |
416 | } |
417 | } |
418 | break; |
419 | |
420 | case O65_OPT_ASM: |
421 | Print (stdout, 1, "O65 assembler option: '%s'\n" , |
422 | GetO65OptionText (O)); |
423 | break; |
424 | |
425 | case O65_OPT_AUTHOR: |
426 | if (Author) { |
427 | xfree (Author); |
428 | } |
429 | Author = xstrdup (GetO65OptionText (O)); |
430 | Print (stdout, 1, "O65 author option: '%s'\n" , Author); |
431 | break; |
432 | |
433 | case O65_OPT_TIMESTAMP: |
434 | Print (stdout, 1, "O65 timestamp option: '%s'\n" , |
435 | GetO65OptionText (O)); |
436 | break; |
437 | |
438 | default: |
439 | Warning ("Found unknown option, type %d, length %d" , |
440 | O->Type, O->Len); |
441 | break; |
442 | } |
443 | } |
444 | |
445 | /* If we shouldn't generate output, we're done here */ |
446 | if (NoOutput) { |
447 | return; |
448 | } |
449 | |
450 | /* Open the output file */ |
451 | F = fopen (OutputName, "w" ); |
452 | if (F == 0) { |
453 | Error ("Cannot open '%s': %s" , OutputName, strerror (errno)); |
454 | } |
455 | |
456 | /* Create a header */ |
457 | fprintf (F, ";\n; File generated by co65 v %s using model '%s'\n;\n" , |
458 | GetVersionAsString (), GetModelName (Model)); |
459 | |
460 | /* Select the CPU */ |
461 | if ((D->Header.mode & O65_CPU_MASK) == O65_CPU_65816) { |
462 | fprintf (F, ".p816\n" ); |
463 | } |
464 | |
465 | /* Object file options */ |
466 | fprintf (F, ".fopt\t\tcompiler,\"co65 v %s\"\n" , GetVersionAsString ()); |
467 | if (Author) { |
468 | fprintf (F, ".fopt\t\tauthor, \"%s\"\n" , Author); |
469 | xfree (Author); |
470 | Author = 0; |
471 | } |
472 | |
473 | /* Several other assembler options */ |
474 | fprintf (F, ".case\t\ton\n" ); |
475 | fprintf (F, ".debuginfo\t%s\n" , (DebugInfo != 0)? "on" : "off" ); |
476 | |
477 | /* Setup/export the segment labels */ |
478 | SetupSegLabels (F); |
479 | |
480 | /* End of header */ |
481 | fprintf (F, "\n" ); |
482 | |
483 | /* Imported identifiers */ |
484 | ConvertImports (F, D); |
485 | |
486 | /* Exported identifiers */ |
487 | ConvertExports (F, D); |
488 | |
489 | /* Code segment */ |
490 | ConvertCodeSeg (F, D); |
491 | |
492 | /* Data segment */ |
493 | ConvertDataSeg (F, D); |
494 | |
495 | /* BSS segment */ |
496 | ConvertBssSeg (F, D); |
497 | |
498 | /* Zero page segment */ |
499 | ConvertZeropageSeg (F, D); |
500 | |
501 | /* End of data */ |
502 | fprintf (F, ".end\n" ); |
503 | fclose (F); |
504 | } |
505 | |