Coverage Report

Created: 2025-10-10 06:24

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/upx/src/p_djgpp2.cpp
Line
Count
Source
1
/* p_djgpp2.cpp --
2
3
   This file is part of the UPX executable compressor.
4
5
   Copyright (C) 1996-2025 Markus Franz Xaver Johannes Oberhumer
6
   Copyright (C) 1996-2025 Laszlo Molnar
7
   All Rights Reserved.
8
9
   UPX and the UCL library are free software; you can redistribute them
10
   and/or modify them under the terms of the GNU General Public License as
11
   published by the Free Software Foundation; either version 2 of
12
   the License, or (at your option) any later version.
13
14
   This program is distributed in the hope that it will be useful,
15
   but WITHOUT ANY WARRANTY; without even the implied warranty of
16
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17
   GNU General Public License for more details.
18
19
   You should have received a copy of the GNU General Public License
20
   along with this program; see the file COPYING.
21
   If not, write to the Free Software Foundation, Inc.,
22
   59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23
24
   Markus F.X.J. Oberhumer              Laszlo Molnar
25
   <markus@oberhumer.com>               <ezerotven+github@gmail.com>
26
 */
27
28
#include "conf.h"
29
#include "file.h"
30
#include "filter.h"
31
#include "packer.h"
32
#include "p_djgpp2.h"
33
#include "linker.h"
34
35
static const CLANG_FORMAT_DUMMY_STATEMENT
36
#include "stub/i386-dos32.djgpp2.h"
37
static const CLANG_FORMAT_DUMMY_STATEMENT
38
#include "stub/i386-dos32.djgpp2-stubify.h"
39
40
/*************************************************************************
41
//
42
**************************************************************************/
43
44
25.4k
PackDjgpp2::PackDjgpp2(InputFile *f) : super(f), coff_offset(0) {
45
25.4k
    bele = &N_BELE_RTP::le_policy;
46
25.4k
    COMPILE_TIME_ASSERT(sizeof(external_scnhdr_t) == 40)
47
25.4k
    COMPILE_TIME_ASSERT(sizeof(coff_header_t) == 0xa8)
48
25.4k
    COMPILE_TIME_ASSERT_ALIGNED1(external_scnhdr_t)
49
25.4k
    COMPILE_TIME_ASSERT_ALIGNED1(coff_header_t)
50
25.4k
    COMPILE_TIME_ASSERT(sizeof(stub_i386_dos32_djgpp2_stubify) == 2048)
51
25.4k
    COMPILE_TIME_ASSERT(STUB_I386_DOS32_DJGPP2_STUBIFY_ADLER32 == 0xbf689ba8)
52
25.4k
    COMPILE_TIME_ASSERT(STUB_I386_DOS32_DJGPP2_STUBIFY_CRC32 == 0x2ae982b2)
53
    // printf("0x%08x\n", upx_adler32(stubify_stub, sizeof(stubify_stub)));
54
    // assert(upx_adler32(stubify_stub, sizeof(stubify_stub)) == STUBIFY_STUB_ADLER32);
55
25.4k
}
56
57
0
Linker *PackDjgpp2::newLinker() const { return new ElfLinkerX86; }
58
59
0
const int *PackDjgpp2::getCompressionMethods(int method, int level) const {
60
0
    return Packer::getDefaultCompressionMethods_le32(method, level);
61
0
}
62
63
0
const int *PackDjgpp2::getFilters() const {
64
0
    static const int filters[] = {0x26, 0x24,           0x49, 0x46, 0x16, 0x13,  0x14,
65
0
                                  0x11, FT_ULTRA_BRUTE, 0x25, 0x15, 0x12, FT_END};
66
0
    return filters;
67
0
}
68
69
unsigned PackDjgpp2::findOverlapOverhead(const byte *buf, const byte *tbuf, unsigned range,
70
0
                                         unsigned upper_limit) const {
71
0
    unsigned o = super::findOverlapOverhead(buf, tbuf, range, upper_limit);
72
0
    o = (o + 0x3ff) & ~0x1ff;
73
0
    return o;
74
0
}
75
76
0
void PackDjgpp2::buildLoader(const Filter *ft) {
77
    // prepare loader
78
0
    initLoader(stub_i386_dos32_djgpp2, sizeof(stub_i386_dos32_djgpp2));
79
0
    addLoader("IDENTSTR,DJ2MAIN1", ft->id ? "DJCALLT1" : "",
80
0
              ph.first_offset_found == 1 ? "DJ2MAIN2" : "",
81
0
              M_IS_LZMA(ph.method) ? "LZMA_INIT_STACK" : "", getDecompressorSections(),
82
0
              M_IS_LZMA(ph.method) ? "LZMA_DONE_STACK" : "", "DJ2BSS00");
83
0
    if (ft->id) {
84
0
        assert(ft->calls > 0);
85
0
        addLoader("DJCALLT2");
86
0
        addFilter32(ft->id);
87
0
    }
88
0
    addLoader("DJRETURN,+40C,UPX1HEAD");
89
0
}
90
91
/*************************************************************************
92
// util
93
**************************************************************************/
94
95
6
void PackDjgpp2::handleStub(OutputFile *fo) {
96
6
    if (fo && !opt->djgpp2_coff.coff) {
97
3
        if (coff_offset > 0) {
98
            // copy stub from exe
99
3
            Packer::handleStub(fi, fo, coff_offset);
100
3
        } else {
101
            // "stubify" stub
102
0
            info("Adding stub: %zd bytes", sizeof(stub_i386_dos32_djgpp2_stubify));
103
0
            fo->write(stub_i386_dos32_djgpp2_stubify, sizeof(stub_i386_dos32_djgpp2_stubify));
104
0
        }
105
3
    }
106
6
}
107
108
31
static bool is_dlm(InputFile *fi, unsigned coff_offset) {
109
31
    byte buf[4];
110
31
    unsigned off;
111
112
31
    try {
113
31
        fi->seek(coff_offset, SEEK_SET);
114
31
        fi->readx(buf, 4);
115
31
        off = get_le32(buf);
116
31
        if (off > coff_offset + 4)
117
26
            return false;
118
5
        fi->seek(off, SEEK_SET);
119
5
        fi->readx(buf, 4);
120
5
        if (memcmp(buf, "DLMF", 4) == 0)
121
2
            return true;
122
5
    } catch (const IOException &) {
123
0
    }
124
3
    return false;
125
31
}
126
127
2
static void handle_allegropak(InputFile *fi, OutputFile *fo) {
128
2
    byte b[8];
129
2
    int pfsize = 0;
130
131
2
    try {
132
2
        fi->seek(-8, SEEK_END);
133
2
        fi->readx(b, 8);
134
2
        if (memcmp(b, "slh+", 4) != 0)
135
2
            return;
136
0
        pfsize = get_be32_signed(b + 4);
137
0
        if (pfsize <= 8 || pfsize >= fi->st.st_size)
138
0
            return;
139
0
        fi->seek(-pfsize, SEEK_END);
140
0
    } catch (const IOException &) {
141
0
        return;
142
0
    }
143
0
    MemBuffer buf(0x4000);
144
0
    while (pfsize > 0) {
145
0
        const int len = UPX_MIN(pfsize, (int) buf.getSize());
146
0
        fi->readx(buf, len);
147
0
        fo->write(buf, len);
148
0
        pfsize -= len;
149
0
    }
150
0
}
151
152
25.4k
int PackDjgpp2::readFileHeader() {
153
25.4k
    dos_header_t dos_hdr;
154
155
25.4k
    fi->seek(0, SEEK_SET);
156
25.4k
    fi->readx(&dos_hdr, sizeof(dos_hdr));
157
25.4k
    if (get_le16(&dos_hdr.e_magic) == 0x5a4d) { // MZ exe signature, stubbed?
158
1.04k
        byte magic[8];
159
1.04k
        fi->seek(16 * get_le16(&dos_hdr.e_cparhdr), SEEK_SET);
160
1.04k
        fi->readx(magic, 8);
161
1.04k
        if (memcmp("go32stub", magic, 8) != 0)
162
760
            return 0; // not V2 image
163
1.04k
    }
164
24.6k
    coff_offset = 512 * get_le16(&dos_hdr.e_cp);
165
24.6k
    if (get_le16(&dos_hdr.e_cblp) != 0)
166
23.8k
        coff_offset += get_le16(&dos_hdr.e_cblp) - 512;
167
24.6k
    fi->seek(coff_offset, SEEK_SET);
168
24.6k
    if (fi->read(&coff_hdr, sizeof(coff_hdr)) != sizeof(coff_hdr))
169
22
        throwCantPack("skipping djgpp symlink");
170
24.6k
    if (coff_hdr.f_magic != 0x014c) // I386MAGIC
171
1.41k
        return 0;
172
23.2k
    if ((coff_hdr.f_flags & 2) == 0) // F_EXEC - COFF executable
173
3
        return 0;
174
23.2k
    if (coff_hdr.a_magic != 0413) // ZMAGIC - demand load format
175
14
        return 0;
176
    // FIXME: check for Linux etc.
177
178
23.2k
    text = &coff_hdr.sh[0];
179
23.2k
    data = &coff_hdr.sh[1];
180
23.2k
    bss = &coff_hdr.sh[2];
181
23.2k
    return UPX_F_DJGPP2_COFF;
182
23.2k
}
183
184
// "strip" debug info
185
0
void PackDjgpp2::stripDebug() {
186
0
    coff_hdr.f_symptr = 0;
187
0
    coff_hdr.f_nsyms = 0;
188
0
    coff_hdr.f_flags = 0x10f; // 0x100: "32 bit machine: LSB first"
189
0
    memset(text->misc, 0, 12);
190
0
}
191
192
/*************************************************************************
193
//
194
**************************************************************************/
195
196
0
tribool PackDjgpp2::canPack() {
197
0
    if (!readFileHeader())
198
0
        return false;
199
0
    if (is_dlm(fi, coff_offset))
200
0
        throwCantPack("can't handle DLM");
201
202
0
    if (opt->force == 0)
203
0
        if (text->size != coff_hdr.a_tsize || data->size != coff_hdr.a_dsize)
204
0
            throwAlreadyPacked();
205
206
    // Check for gap in vaddr between text and data, or between data and bss.
207
0
    if (text->vaddr + text->size != data->vaddr || data->vaddr + data->size != bss->vaddr) {
208
        // "Non-standard" layout of text,data,bss: not contiguous in vaddr.
209
        // But should be OK if no overlap.
210
211
        // Check for no overlap of text and data:
212
        // neither by vaddr, nor by image data
213
0
        if (text->vaddr + text->size <= data->vaddr &&
214
0
            data->scnptr - text->scnptr <= data->vaddr - text->vaddr) {
215
            // Examples: Quake1; FreePascal(DOS) install.exe (github-issue45)
216
            // Hack: enlarge text image data to eliminate the gap.
217
0
            text->size = coff_hdr.a_tsize = data->scnptr - text->scnptr;
218
            // But complain if this causes overlap in vaddr
219
0
            if (text->vaddr + text->size > data->vaddr)
220
0
                throwAlreadyPacked();
221
0
        } else
222
0
            throwAlreadyPacked();
223
0
    }
224
    // FIXME: check for Linux etc.
225
0
    return true;
226
0
}
227
228
/*************************************************************************
229
//
230
**************************************************************************/
231
232
0
void PackDjgpp2::pack(OutputFile *fo) {
233
0
    handleStub(fo);
234
235
    // patch coff header #1: "strip" debug info
236
0
    stripDebug();
237
238
    // read file
239
0
    const unsigned size = text->size + data->size;
240
0
    const unsigned tpos = text->scnptr;
241
0
    const unsigned hdrsize = 20 + 28 + mem_size(sizeof(external_scnhdr_t), coff_hdr.f_nscns);
242
0
    const unsigned usize = size + hdrsize;
243
0
    if (hdrsize < sizeof(coff_hdr) || hdrsize > tpos)
244
0
        throwCantPack("coff header error");
245
246
0
    ibuf.alloc(usize);
247
0
    obuf.allocForCompression(usize);
248
249
0
    fi->seek(coff_offset, SEEK_SET);
250
0
    fi->readx(ibuf, hdrsize); // orig. coff header
251
0
    fi->seek(coff_offset + tpos, SEEK_SET);
252
0
    fi->readx(ibuf + hdrsize, size);
253
254
    // prepare packheader
255
0
    ph.u_len = usize;
256
    // prepare filter
257
0
    Filter ft(ph.level);
258
0
    ft.buf_len = usize - data->size;
259
0
    ft.addvalue = text->vaddr - hdrsize;
260
    // compress
261
0
    upx_compress_config_t cconf;
262
0
    cconf.reset();
263
    // limit stack size needed for runtime decompression
264
0
    cconf.conf_lzma.max_num_probs = 1846 + (768 << 4); // ushort: ~28 KiB stack
265
0
    compressWithFilters(&ft, 512, &cconf);
266
267
    // patch coff header #2
268
0
    const unsigned lsize = getLoaderSize();
269
0
    assert(lsize % 4 == 0);
270
0
    text->size = lsize;    // new size of .text
271
0
    data->size = ph.c_len; // new size of .data
272
273
0
    unsigned stack = 1024 + ph.overlap_overhead + getDecompressorWrkmemSize();
274
0
    stack = ALIGN_UP(stack, 16u);
275
0
    if (bss->size < stack) // give it a .bss
276
0
        bss->size = stack;
277
278
0
    text->scnptr = sizeof(coff_hdr);
279
0
    data->scnptr = text->scnptr + text->size;
280
0
    data->vaddr = bss->vaddr + ((data->scnptr + data->size) & 0x1ff) - data->size +
281
0
                  ph.overlap_overhead - 0x200;
282
0
    coff_hdr.f_nscns = 3;
283
284
0
    linker->defineSymbol("original_entry", coff_hdr.a_entry);
285
0
    linker->defineSymbol("length_of_bss", ph.overlap_overhead / 4);
286
0
    defineDecompressorSymbols();
287
    // Just need no overlap; non-contiguous (gap length > 0)) is OK
288
0
    assert(bss->vaddr >= ((size + 0x1ff) & ~0x1ff) + (text->vaddr & ~0x1ff));
289
0
    linker->defineSymbol("stack_for_lzma", bss->vaddr + bss->size);
290
0
    linker->defineSymbol("start_of_uncompressed", text->vaddr - hdrsize);
291
0
    linker->defineSymbol("start_of_compressed", data->vaddr);
292
0
    defineFilterSymbols(&ft);
293
294
    // we should not overwrite our decompressor during unpacking
295
    // the original coff header (which is put just before the
296
    // beginning of the original .text section)
297
0
    assert(text->vaddr > hdrsize + lsize + sizeof(coff_hdr));
298
299
    // patch coff header #3
300
0
    text->vaddr = sizeof(coff_hdr);
301
0
    coff_hdr.a_entry = sizeof(coff_hdr) + getLoaderSection("DJ2MAIN1");
302
0
    bss->vaddr += ph.overlap_overhead;
303
0
    bss->size -= ph.overlap_overhead;
304
305
    // because of a feature (bug?) in stub.asm we need some padding
306
0
    memcpy(obuf + data->size, "UPX", 3);
307
0
    data->size = ALIGN_UP(data->size, 4u);
308
309
0
    linker->defineSymbol("DJ2MAIN1", coff_hdr.a_entry);
310
0
    relocateLoader();
311
312
    // prepare loader
313
0
    MemBuffer loader(lsize);
314
0
    memcpy(loader, getLoader(), lsize);
315
0
    patchPackHeader(loader, lsize);
316
317
    // write coff header, loader and compressed file
318
0
    fo->write(&coff_hdr, sizeof(coff_hdr));
319
0
    fo->write(loader, lsize);
320
0
    if (opt->debug.dump_stub_loader)
321
0
        OutputFile::dump(opt->debug.dump_stub_loader, loader, lsize);
322
0
    fo->write(obuf, data->size);
323
#if 0
324
    printf("%-13s: coff hdr   : %8d bytes\n", getName(), (int) sizeof(coff_hdr));
325
    printf("%-13s: loader     : %8d bytes\n", getName(), (int) lsize);
326
    printf("%-13s: compressed : %8d bytes\n", getName(), (int) data->size);
327
#endif
328
329
    // verify
330
0
    verifyOverlappingDecompression();
331
332
    // handle overlay
333
    // FIXME: only Allegro pakfiles are supported
334
0
    handle_allegropak(fi, fo);
335
336
    // finally check the compression ratio
337
0
    if (!checkFinalCompressionRatio(fo))
338
0
        throwNotCompressible();
339
0
}
340
341
/*************************************************************************
342
//
343
**************************************************************************/
344
345
25.4k
tribool PackDjgpp2::canUnpack() {
346
25.4k
    if (!readFileHeader())
347
2.19k
        return false;
348
23.2k
    if (is_dlm(fi, coff_offset))
349
2
        throwCantUnpack("can't handle DLM");
350
23.2k
    fi->seek(coff_offset, SEEK_SET);
351
23.2k
    return readPackHeader(4096) ? 1 : -1;
352
23.2k
}
353
354
/*************************************************************************
355
//
356
**************************************************************************/
357
358
6
void PackDjgpp2::unpack(OutputFile *fo) {
359
6
    handleStub(fo);
360
361
6
    ibuf.alloc(ph.c_len);
362
6
    obuf.allocForDecompression(ph.u_len);
363
364
6
    fi->seek(coff_offset + ph.buf_offset + ph.getPackHeaderSize(), SEEK_SET);
365
6
    fi->readx(ibuf, ph.c_len);
366
367
    // decompress
368
6
    decompress(ibuf, obuf);
369
370
6
    coff_header_t *const chdr = (coff_header_t *) raw_bytes(obuf, sizeof(coff_header_t));
371
6
    text = &chdr->sh[0];
372
6
    data = &chdr->sh[1];
373
6
    bss = &chdr->sh[2];
374
375
6
    const unsigned hdrsize = 20 + 28 + mem_size(sizeof(external_scnhdr_t), chdr->f_nscns);
376
6
    if (hdrsize < sizeof(coff_hdr) || hdrsize > text->scnptr || hdrsize > ph.u_len)
377
0
        throwCantUnpack("coff header error");
378
379
6
    unsigned addvalue;
380
6
    if (ph.version >= 14)
381
4
        addvalue = text->vaddr - hdrsize;
382
2
    else
383
2
        addvalue = text->vaddr & ~0x1ff; // for old versions
384
385
    // unfilter
386
6
    if (ph.filter) {
387
4
        Filter ft(ph.level);
388
4
        ft.init(ph.filter, addvalue);
389
4
        ft.cto = (byte) ph.filter_cto;
390
4
        if (ph.version < 11) {
391
0
            byte ctobuf[4];
392
0
            fi->readx(ctobuf, 4);
393
0
            ft.cto = (byte) (get_le32(ctobuf) >> 24);
394
0
        }
395
4
        ft.unfilter(obuf, ph.u_len - data->size);
396
4
    }
397
398
6
    if (ph.version < 14) {
399
        // fixup for the aligning bug in strip 2.8+
400
0
        text->scnptr &= 0x1ff;
401
0
        data->scnptr = text->scnptr + text->size;
402
        // write decompressed file
403
0
        if (fo)
404
0
            fo->write(obuf, ph.u_len);
405
6
    } else {
406
        // write the header
407
        // some padding might be required between the end
408
        // of the header and the start of the .text section
409
410
6
        const unsigned padding = text->scnptr - hdrsize;
411
6
        ibuf.clear(0, padding);
412
413
6
        if (fo) {
414
2
            fo->write(obuf, hdrsize);
415
2
            fo->write(ibuf, padding);
416
2
            fo->write(obuf + hdrsize, ph.u_len - hdrsize);
417
2
        }
418
6
    }
419
420
6
    if (fo)
421
2
        handle_allegropak(fi, fo);
422
6
}
423
424
/* vim:set ts=4 sw=4 et: */