This patch introduces extra functionality to chain.c, mainly with reference to BPB adjustments, but not only that. It expects 3 small patches I sent earlier (they are included for easy reference, patches 1-3/4). The changes introduced are: 1) file and boot sector use separate options to control load address and jump address (if applicable). Options are as described below: * segbs=<seg[:off[:ip]]> * main control of where the sector (boot sector or mbr) should be loaded * at, and where to actually jump. Default value is 0:7C00:7C00. Numbers * are parsed as *HEX* values. Of course, if you set custom segbs without * specifying some of the values (or leaving empty field near colon), * unspecified ones are assumed 0. Overall, sector is loaded at seg:off, * and the jump is made to seg:ip. * * seg=<seg[:off[:ip]]> * similary to segbs option, this controls where to load file (if * applicable - see file= option and derivatives). File takes precedence * over boot sector, so in case their areas overlap, only file will be * loaded. To retain compatibility with older configs simple invocation such as seg=0x60 is perfectly valid. Only potential incompatibilities here, is if someone used decimal values (e.g. seg=96) or if someone used seg= to actually control boot sector address/jump (extremely unlikely). Old logic of load_base used first for file, then for sector is no longer present. 2) Overlapping regions are checked very carefully. File takes precedence over boot sector, and boot sector over handover area - see the patch, near boot sector loading and handover area creation. 3) bsnomap option allows reading/adjusting/writing boot sector, without mapping it to real memory before chainloading. Useful for adjusting BPB without being forced to set proper segbs= address. 4) All win/dos file options have been updated accordingly, with rich comments. 5) setdrive[@<offset>] option: * update the "drive unit" field in a FAT/NTFS boot sector. Offset should * be either 0x24 (FAT/NTFS excluding FAT32) or 0x40 (FAT32 only) - * chainloader won't accept other values. Offset is parsed as a *HEX* * number. Offsetless value defaults to 0x24. 6) setgeometry - for legacy spt and heads fields in BPB 7) setbpb * A shortcut that enables set{hidden,geometry} options; * setdrive is not covered by this shortcut, due to the ambiguity. 8) writebs, filebpb * writebs * Write updated boot sector to the disk. This is performed only * if some of the BPB's fields actually changed. * * filebpb * An option that let loaded file be treated as BPB compatible. If any of * the previous set* options is specified and file is being loaded, BPB at * appropriate offsets will be adjusted accordingly. Obviously, writebs * options is ignored for file. It can be used for crude sort-of emulation * of syslinux's native .BSS capability, where BPB patching is limited only * to options specified by set* (but it reflects actual BIOS imaginations) Other changes: - argument parsing is moved outside main - cosmetic text fixes Further stuff to do: - some more tests with unusual configs - splitting main into smaller chunks - doc/chain.txt Michal Soltys (4): chain.c: mismatch between index and mbr_index in mbr iterator chain.c: gpt's index/private.index mismatch fix, cosmetic iterator changes chain.c: allocation fixes chain.c: New functionality com32/modules/chain.c | 611 +++++++++++++++++++++++++++++++++++------------- 1 files changed, 445 insertions(+), 166 deletions(-)
Michal Soltys
2010-Jul-26 22:50 UTC
[syslinux] [PATCH 1/4] chain.c: mismatch between index and mbr_index in mbr iterator
When primary partitions are iterated, non-data partitions should also be counted. For example, if we have data partitions #1, #2, #4 and empty partition #3, then chain.c32 hd0 4 would fail. This patch fixes this. Signed-off-by: Michal Soltys <soltys at ziu.info> --- com32/modules/chain.c | 4 ++-- 1 files changed, 2 insertions(+), 2 deletions(-) diff --git a/com32/modules/chain.c b/com32/modules/chain.c index 48a83d2..91db9f7 100644 --- a/com32/modules/chain.c +++ b/com32/modules/chain.c @@ -624,8 +624,8 @@ static struct disk_part_iter *next_mbr_part(struct disk_part_iter *part) /* Update parameters to reflect this new partition. Re-use iterator */ part->lba_data = table[part->private.mbr_index].start_lba; - dprintf("Partition %d primary lba %u\n", part->index, part->lba_data); - part->index++; + dprintf("Partition %d primary lba %u\n", part->private.mbr_index, part->lba_data); + part->index = part->private.mbr_index + 1; part->record = table + part->private.mbr_index; return part; -- 1.6.3.1
Michal Soltys
2010-Jul-26 22:50 UTC
[syslinux] [PATCH 2/4] chain.c: gpt's index/private.index mismatch fix, cosmetic iterator changes
1) public index in gpt iterator should not skip empty partitions, e.g. if we have 1st and 3rd gpt entry used, we expect the numbers to be 1 and 3, not 1 and 2 (similary to how linux sees it e.g. /dev/sda1 and /dev/sda3). It's analogous to index / mbr_index fix. 2) free(ebr_part); in mbr iterator was not reachable. ebr iterator takes care of freeing itself and its parent. 3) in ebr iterator, error jump from "insane" partition error can simply go to err_ebr. Signed-off-by: Michal Soltys <soltys at ziu.info> --- com32/modules/chain.c | 10 ++-------- 1 files changed, 2 insertions(+), 8 deletions(-) diff --git a/com32/modules/chain.c b/com32/modules/chain.c index 91db9f7..b5c1d8f 100644 --- a/com32/modules/chain.c +++ b/com32/modules/chain.c @@ -558,7 +558,7 @@ static struct disk_part_iter *next_ebr_part(struct disk_part_iter *part) if (ebr_table[0].start_lba >= extended->start_lba + extended->length) { dprintf("Insane logical partition!\n"); - goto err_insane; + goto err_ebr; } } /* Success */ @@ -568,12 +568,7 @@ static struct disk_part_iter *next_ebr_part(struct disk_part_iter *part) part->record = ebr_table; return part; -err_insane: - - free(part->block); - part->block = NULL; err_ebr: - out_finished: free(part->private.ebr.parent->block); free(part->private.ebr.parent); @@ -629,7 +624,6 @@ static struct disk_part_iter *next_mbr_part(struct disk_part_iter *part) part->record = table + part->private.mbr_index; return part; - free(ebr_part); err_alloc: free(part->block); @@ -869,7 +863,7 @@ static struct disk_part_iter *next_gpt_part(struct disk_part_iter *part) part->private.gpt.part_guid = &gpt_part->uid; part->private.gpt.part_label = gpt_part->name; /* Update our index */ - part->index++; + part->index = part->private.gpt.index + 1; gpt_part_dump(gpt_part); /* In a GPT scheme, we re-use the iterator */ -- 1.6.3.1
1) At the end of main, there's attempt to free cur_part->record, which rarely comes from malloc. Only valid case is if gpt handover was performed and chaining was not successful (cur_part->record is overwritten with gpt specifc handover record). Freeing the handover area has been adjusted. 2) If our current iterator is ebr, parent wouldn't be freed at the end of main. Added generic function to free iterators, used by iterators themselves and in main. 3) Malloced areas from read sector and/or file should be freed as well. Signed-off-by: Michal Soltys <soltys at ziu.info> --- com32/modules/chain.c | 112 ++++++++++++++++++++++++++++--------------------- 1 files changed, 64 insertions(+), 48 deletions(-) diff --git a/com32/modules/chain.c b/com32/modules/chain.c index b5c1d8f..6c3bb64 100644 --- a/com32/modules/chain.c +++ b/com32/modules/chain.c @@ -464,6 +464,9 @@ struct disk_part_iter; typedef struct disk_part_iter *(*disk_part_iter_func) (struct disk_part_iter * part); +/* Forward declaration */ +static struct disk_part_iter *next_ebr_part(struct disk_part_iter *part); + /* Contains details for a partition under examination */ struct disk_part_iter { /* The block holding the table we are part of */ @@ -505,6 +508,21 @@ struct disk_part_iter { } private; }; +static void free_iter(struct disk_part_iter *part) +{ + struct disk_part_iter *cur; + + while(part) { + cur = part; + if(part->next == next_ebr_part) + part = part->private.ebr.parent; + else + part = NULL; + free(cur->block); + free(cur); + } +} + static struct disk_part_iter *next_ebr_part(struct disk_part_iter *part) { const struct part_entry *ebr_table; @@ -570,10 +588,7 @@ static struct disk_part_iter *next_ebr_part(struct disk_part_iter *part) err_ebr: out_finished: - free(part->private.ebr.parent->block); - free(part->private.ebr.parent); - free(part->block); - free(part); + free_iter(part); return NULL; } @@ -603,10 +618,9 @@ static struct disk_part_iter *next_mbr_part(struct disk_part_iter *part) error("Could not allocate extended partition iterator!\n"); goto err_alloc; } + memset(ebr_part, 0, sizeof(*ebr_part)); /* Setup EBR iterator parameters */ - ebr_part->block = NULL; ebr_part->index = 4; - ebr_part->record = NULL; ebr_part->next = next_ebr_part; ebr_part->private.ebr.parent = part; /* Trigger an initial EBR load */ @@ -625,9 +639,7 @@ static struct disk_part_iter *next_mbr_part(struct disk_part_iter *part) return part; err_alloc: - - free(part->block); - free(part); + free_iter(part); return NULL; } @@ -870,9 +882,7 @@ static struct disk_part_iter *next_gpt_part(struct disk_part_iter *part) return part; err_last: - free(part->block); - free(part); - + free_iter(part); return NULL; } @@ -889,6 +899,7 @@ static struct disk_part_iter *get_first_partition(struct disk_part_iter *part) error("Count not allocate partition iterator!\n"); goto err_alloc_iter; } + memset(part, 0, sizeof(*part)); /* Read MBR */ part->block = read_sectors(0, 2); if (!part->block) { @@ -940,16 +951,10 @@ static struct disk_part_iter *get_first_partition(struct disk_part_iter *part) return part->next(part); err_gpt_table: - err_mbr: - - free(part->block); - part->block = NULL; err_read_mbr: - - free(part); err_alloc_iter: - + free_iter(part); return NULL; } @@ -1292,6 +1297,9 @@ int main(int argc, char *argv[]) struct mbr *mbr = NULL; char *p; struct disk_part_iter *cur_part = NULL; + void *sect_area = NULL, *file_area = NULL; + struct part_entry *hand_area = NULL; + struct syslinux_rm_regs regs; char *drivename, *partition; int hd, drive, whichpart = 0; /* MBR by default */ @@ -1523,6 +1531,7 @@ int main(int argc, char *argv[]) goto bail; } data[ndata].base = load_base; + file_area = (void *)data[ndata].data; load_base = 0x7c00; /* If we also load a boot sector */ /* Create boot info table: needed when you want to chainload @@ -1701,7 +1710,9 @@ int main(int argc, char *argv[]) } else if (!(data[ndata].data = read_sectors(cur_part->lba_data, 1))) { error("Cannot read boot sector\n"); goto bail; - } + } else + sect_area = (void *)data[ndata].data; + data[ndata].size = SECTOR; data[ndata].base = load_base; @@ -1740,7 +1751,6 @@ int main(int argc, char *argv[]) if (cur_part) { if (cur_part->next == next_gpt_part) { /* Do GPT hand-over, if applicable (as per syslinux/doc/gpt.txt) */ - struct part_entry *record; /* Look at the GPT partition */ const struct gpt_part *gp = (const struct gpt_part *) (cur_part->block + @@ -1748,73 +1758,79 @@ int main(int argc, char *argv[]) /* Note the partition length */ uint64_t lba_count = gp->lba_last - gp->lba_first + 1; /* The length of the hand-over */ - int synth_size + uint32_t synth_size sizeof(struct part_entry) + sizeof(uint32_t) + cur_part->private.gpt.size; /* Will point to the partition record length in the hand-over */ uint32_t *plen; /* Allocate the hand-over record */ - record = malloc(synth_size); - if (!record) { + hand_area = malloc(synth_size); + if (!hand_area) { error("Could not build GPT hand-over record!\n"); goto bail; } /* Synthesize the record */ - memset(record, 0, synth_size); - record->active_flag = 0x80; - record->ostype = 0xED; + memset(hand_area, 0, synth_size); + hand_area->active_flag = 0x80; + hand_area->ostype = 0xED; /* All bits set by default */ - record->start_lba = ~(uint32_t) 0; - record->length = ~(uint32_t) 0; + hand_area->start_lba = ~(uint32_t) 0; + hand_area->length = ~(uint32_t) 0; /* If these fit the precision, pass them on */ - if (cur_part->lba_data < record->start_lba) - record->start_lba = cur_part->lba_data; - if (lba_count < record->length) - record->length = lba_count; + if (cur_part->lba_data < hand_area->start_lba) + hand_area->start_lba = cur_part->lba_data; + if (lba_count < hand_area->length) + hand_area->length = lba_count; /* Next comes the GPT partition record length */ - plen = (uint32_t *) (record + 1); + plen = (uint32_t *) (hand_area + 1); plen[0] = cur_part->private.gpt.size; /* Next comes the GPT partition record copy */ memcpy(plen + 1, gp, plen[0]); - cur_part->record = record; regs.eax.l = 0x54504721; /* '!GPT' */ data[ndata].base = 0x7be; data[ndata].size = synth_size; - data[ndata].data = (void *)record; + data[ndata].data = (void *)hand_area; ndata++; regs.esi.w[0] = 0x7be; dprintf("GPT handover:\n"); - mbr_part_dump(record); + mbr_part_dump(hand_area); gpt_part_dump((struct gpt_part *)(plen + 1)); } else if (cur_part->record) { /* MBR handover protocol */ - static struct part_entry handover_record; - handover_record = *cur_part->record; - handover_record.start_lba = cur_part->lba_data; + /* Allocate the hand-over record */ + hand_area = malloc(sizeof(struct part_entry)); + if (!hand_area) { + error("Could not build MBR hand-over record!\n"); + goto bail; + } + + memcpy(hand_area, cur_part->record, sizeof(struct part_entry)); + hand_area->start_lba = cur_part->lba_data; data[ndata].base = 0x7be; - data[ndata].size = sizeof handover_record; - data[ndata].data = &handover_record; + data[ndata].size = sizeof(struct part_entry); + data[ndata].data = (void *)hand_area; ndata++; regs.esi.w[0] = 0x7be; dprintf("MBR handover:\n"); - mbr_part_dump(&handover_record); + mbr_part_dump(hand_area); } } do_boot(data, ndata, ®s); bail: - if (cur_part) { - free(cur_part->block); - free((void *)cur_part->record); - } - free(cur_part); + /* Free iterator */ + free_iter(cur_part); free(mbr); + /* Free allocated areas */ + free(file_area); + free(sect_area); + free(hand_area); return 255; } -- 1.6.3.1
1) Updated seg and segbs options to control load and jump addresses precisely. 2) Carefully check overlapping regions. 3) bsnomap option 4) Update all win/dos related options. 5) setgeometry option 6) setdrive[@<offset>] option 7) setbpb shortcut option 8) writebs, filebpb options Signed-off-by: Michal Soltys <soltys at ziu.info> --- com32/modules/chain.c | 498 ++++++++++++++++++++++++++++++++++++++----------- 1 files changed, 389 insertions(+), 109 deletions(-) diff --git a/com32/modules/chain.c b/com32/modules/chain.c index 6c3bb64..82654cf 100644 --- a/com32/modules/chain.c +++ b/com32/modules/chain.c @@ -45,12 +45,28 @@ * * Options: * + * segbs=<seg[:off[:ip]]> + * main control of where the sector (boot sector or mbr) should be loaded + * at, and where to actually jump. Default value is 0:7C00:7C00. Numbers + * are parsed as *HEX* values. Of course, if you set custom segbs without + * specifying some of the values (or leaving empty field near colon), + * unspecified ones are assumed 0. Overall, sector is loaded at seg:off, + * and the jump is made to seg:ip. + * + * seg=<seg[:off[:ip]]> + * similary to segbs option, this controls where to load file (if + * applicable - see file= option and derivatives). File takes precedence + * over boot sector, so in case their areas overlap, only file will be + * loaded. + * + * bsnomap + * load sector, but don't map it into real memory before chainloading. + * Useful for fixing BPB values in DOS cases. + * * file=<loader> * loads the file <loader> **from the Syslinux filesystem** - * instead of loading the boot sector. - * - * seg=<segment> - * loads at and jumps to <seg>:0000 instead of 0000:7C00. + * Boot sector will be loaded as well, if possible. Then handover area + * will be prepared - again - if possible. * * isolinux=<loader> * chainload another version/build of the ISOLINUX bootloader and patch @@ -59,26 +75,58 @@ * when you want more than one ISOLINUX per CD/DVD. * * ntldr=<loader> - * equivalent to seg=0x2000 file=<loader> sethidden, - * used with WinNT's loaders + * equivalent of file=<loader> seg=2000 setbpb setdrive writebs, + * used with WinNT's loaders. This is a bit interesting, actually + * required BPB adjustments are: + * + * - valid drive unit (which also has to be 0x80) + * - sethidden value in memory matching the one written to the disk + * + * Geometry settings stored in boot sector seems to be ignored, "hidden + * sectors" /can/ be junk, as long as on-disk and in-memory values match. + * To boot from other disks, you will have to use swap option. If by some + * miracle you still have some NTish system on FAT32, add setdrive at 40 to + * override default 0x24. + * + * *NOTE* though - if you plan to boot windows directly through its + * bootsector (without loading file), you will need ALL BPB fields valid + * and written to the disk (for partitions starting under 8GB, bootsector + * and possiby $Boot insist on using CHS functions ...), e.g. + * chain.c32 hd2 2 setbpb setdrive swap writebs. * * cmldr=<loader> * used with Recovery Console of Windows NT/2K/XP. - * same as ntldr=<loader> & "cmdcons\0" written to - * the system name field in the bootsector + * same as ntldr=<loader> with "cmdcons\0" written to + * the system name field in the boot sector * * freedos=<loader> - * equivalent to seg=0x60 file=<loader> sethidden, - * used with FreeDOS' kernel.sys. + * equivalent to file=<loader> seg=60 setbpb writebs bsnomap, + * used with FreeDOS' kernel.sys. FreeDOS requires proper geometry written + * to the disk. BPB's fields "hidden sectors" and "drive unit" seem to be + * ignored - FreeDOS rescans all fixed disks and assigns drive letters + * accordingly. + * + * If you want C: to match the disk you're booting from, you will likely + * have to use swap option - possibly with hide option, if you have other + * primary partitions before the one you're booting from. You can add + * appropriate setdrive@ option to keep things nice and tidy, depending on + * your filesystem. * * msdos=<loader> * pcdos=<loader> - * equivalent to seg=0x70 file=<loader> sethidden, - * used with DOS' io.sys. + * equivalent to file=<loader> seg=70 setbpb writebs bsnomap, + * used with DOS' io.sys. Comments the same as in freedos= case. + * TODO: test with live M$ dos system(s), not clones, to check exact + * behaviour. + * + * msdos7=<loader> + * equivalent to file=<loader> seg=70::200 setbpb writebs bsnomap, + * DOS 7+ bootsectors jump to 0x200 after loading io.sys, instead of 0 as + * it was in pre-win95 age. * * grub=<loader> - * same as seg=0x800 file=<loader> & jumping to seg 0x820, - * used with GRUB Legacy stage2 files. + * same as file=<loader> seg=800::200, used with GRUB Legacy stage2 + * files. * * grubcfg=<filename> * set an alternative config filename in stage2 of Grub Legacy, @@ -95,11 +143,38 @@ * hide * change type of primary partitions with IDs 01, 04, 06, 07, * 0b, 0c, or 0e to 1x, except for the selected partition, which - * is converted the other way. + * is converted the other way. Currently this works only within + * boundaries of primary mbr (partitions 1 - 4). * * sethidden - * update the "hidden sectors" (partition offset) field in a - * FAT/NTFS boot sector. + * update the "hidden sectors" (partition offset) field in a FAT/NTFS boot + * sector. + * + * setgeometry + * update the "sectors per track" and "heads" fields in a FAT/NTFS boot + * sector. + * + * setdrive[@<offset>] + * update the "drive unit" field in a FAT/NTFS boot sector. Offset should + * be either 0x24 (FAT/NTFS excluding FAT32) or 0x40 (FAT32 only) - + * chainloader won't accept other values. Offset is parsed as a *HEX* + * number. Offsetless value defaults to 0x24. + * + * setbpb + * A shortcut that enables set{hidden,geometry} options; + * setdrive is not covered by this shortcut, due to the ambiguity. + * + * writebs + * Write updated boot sector to the disk. This is performed only + * if some of the BPB's fields actually changed. + * + * filebpb + * An option that let loaded file be treated as BPB compatible. If any of + * the previous set* options is specified and file is being loaded, BPB at + * appropriate offsets will be adjusted accordingly. Obviously, writebs + * options is ignored for file. It can be used for crude sort-of emulation + * of syslinux's native .BSS capability, where BPB patching is limited only + * to options specified by set* (but it reflects actual BIOS imaginations). * * keeppxe * keep the PXE and UNDI stacks in memory (PXELINUX only). @@ -120,11 +195,19 @@ #include <syslinux/video.h> #define SECTOR 512 /* bytes/sector */ +#define ADDRMAX 0x9F000 +#define ADDRMIN 0x500 static struct options { const char *loadfile; + uint32_t file_lin; + uint16_t file_seg; + uint16_t file_ip; + uint32_t sect_lin; + uint16_t sect_seg; + uint16_t sect_ip; + uint32_t drvoff; uint16_t keeppxe; - uint16_t seg; bool isolinux; bool cmldr; bool grub; @@ -133,6 +216,10 @@ static struct options { bool swap; bool hide; bool sethidden; + bool setgeometry; + bool writebs; + bool filebpb; + bool bsnomap; } opt; struct data_area { @@ -173,8 +260,8 @@ struct diskinfo { int disk; int ebios; /* EBIOS supported on this disk */ int cbios; /* CHS geometry is valid */ - int head; - int sect; + unsigned int head; + unsigned int sect; } disk_info; static int get_disk_params(int disk) @@ -1272,89 +1359,158 @@ Usage: chain.c32 [options]\n\ chain.c32 guid:<guid> [<partition>] [options]\n\ chain.c32 label:<label> [<partition>] [options]\n\ chain.c32 boot [<partition>] [options]\n\ - chain.c32 fs [options]\n\ -Options: file=<loader> Load and execute file, instead of boot sector\n\ - isolinux=<loader> Load another version of ISOLINUX\n\ - ntldr=<loader> Load Windows NTLDR, SETUPLDR.BIN or BOOTMGR\n\ - cmldr=<loader> Load Recovery Console of Windows NT/2K/XP/2003\n\ - freedos=<loader> Load FreeDOS KERNEL.SYS\n\ - msdos=<loader> Load MS-DOS IO.SYS\n\ - pcdos=<loader> Load PC-DOS IBMBIO.COM\n\ - grub=<loader> Load GRUB Legacy stage2\n\ - grubcfg=<filename> Set alternative config filename for GRUB Legacy\n\ - grldr=<loader> Load GRUB4DOS grldr\n\ - seg=<segment> Jump to <seg>:0000, instead of 0000:7C00\n\ - swap Swap drive numbers, if bootdisk is not fd0/hd0\n\ - hide Hide primary partitions, except selected partition\n\ - sethidden Set the FAT/NTFS hidden sectors field\n\ - keeppxe Keep the PXE and UNDI stacks in memory (PXELINUX)\n\ + chain.c32 fs [options]\n\n\ +Options:\n\ + file=<loader> Load and execute file; boot sector is also\n\ + loaded if possible\n\ + isolinux=<loader> Load another version of ISOLINUX\n\ + ntldr=<loader> Load Windows NTLDR, SETUPLDR.BIN or BOOTMGR\n\ + cmldr=<loader> Load Recovery Console of Windows NT/2K/XP/2003\n\ + freedos=<loader> Load FreeDOS KERNEL.SYS\n\ + msdos=<loader> Load MS-DOS IO.SYS\n\ + pcdos=<loader> Load PC-DOS IBMBIO.COM\n\ + msdos7=<loader> Load MS-DOS 7+ IO.SYS\n\ + grub=<loader> Load GRUB Legacy stage2\n\ + grubcfg=<filename> Set alternative config filename for GRUB Legacy\n\ + grldr=<loader> Load GRUB4DOS grldr\n\ + seg=<s[:o[:i]]> Load file at <s:o>, jump to <s:i>\n\ + segbs=<s[:o[:i]]> Load boot sector at <s:o>, jump to <s:i>;\n\ + file takes precedence over boot sector, if loaded\n\ + swap Swap drive numbers, if bootdisk is not fd0/hd0\n\ + hide Hide primary partitions, except selected partition\n\ + sethidden Set the FAT/NTFS hidden sectors field\n\ + setgeometry Set the FAT/NTFS sectors per track and heads fields\n\ + setdrive@<offset> Set the FAT/NTFS drive unit field at <offset>;\n\ + setdrive option alone defaults to 0x24\n\ + setbpb Enable set{hidden,geometry} options\n\ + writebs Write updated boot sector to the disk\n\ + filebpb Also mangle file with bpb options\n\ + bsnomap Don't map boot sector into real memory.\n\ + keeppxe Keep the PXE and UNDI stacks in memory (PXELINUX)\n\n\ See syslinux/com32/modules/chain.c for more information\n"; error(usage); } -int main(int argc, char *argv[]) +/* Adjust BPB options according to chosen options */ + +static void mangle_bpb(void *bootr, uint32_t sec, uint8_t drv) { - struct mbr *mbr = NULL; + if(opt.setgeometry && disk_info.cbios) + *(uint32_t *)((char *)bootr + 0x18) = (disk_info.head << 16 ) | disk_info.sect; + if(opt.sethidden) + *(uint32_t *)((char *)bootr + 0x1C) = sec; + if(opt.drvoff < 512) + *(uint8_t *)((char *)bootr + opt.drvoff) = (opt.swap ? drv & 0x80 : drv); +} + +/* Convert seg:off:ip values into numerical seg:linear_address:ip */ + +static int segseq2vals(char *ptr, uint16_t *seg, uint32_t *lin, uint16_t *ip) +{ + uint32_t segval = 0, offval = 0, ipval = 0, val; char *p; - struct disk_part_iter *cur_part = NULL; - void *sect_area = NULL, *file_area = NULL; - struct part_entry *hand_area = NULL; - struct syslinux_rm_regs regs; - char *drivename, *partition; - int hd, drive, whichpart = 0; /* MBR by default */ - int i; - uint64_t fs_lba = 0; /* Syslinux partition */ - uint32_t file_lba = 0; - struct guid gpt_guid; - unsigned char *isolinux_bin; - uint32_t *checksum, *chkhead, *chktail; - struct data_area data[3]; - int ndata = 0; - addr_t load_base; - static const char cmldr_signature[8] = "cmdcons"; + segval = strtoul(ptr, &p, 16); + if(*p == ':') + offval = strtoul(p+1, &p, 16); + if(*p == ':') + ipval = strtoul(p+1, NULL, 16); - openconsole(&dev_null_r, &dev_stdcon_w); + offval = (segval << 4) + offval; - drivename = "boot"; - partition = NULL; + if (offval < ADDRMIN || offval > ADDRMAX) { + error("Invalid seg or segbs address.\n"); + goto bail; + } - /* Prepare the register set */ - memset(®s, 0, sizeof regs); + val = (segval << 4) + ipval; + + if (ipval > 0xFFFE || val < ADDRMIN || val > ADDRMAX) { + error("Invalid seg or segbs ip value.\n"); + goto bail; + } + + if(seg) + *seg = (uint16_t)segval; + if(lin) + *lin = offval; + if(ip) + *ip = (uint16_t)ipval; + + return 0; + +bail: + return -1; +} + +static int parse_args(int argc, char *argv[], char **drivename, char **partition) +{ + uint32_t val; + int i; + char *p; + + *drivename = "boot"; + *partition = NULL; for (i = 1; i < argc; i++) { if (!strncmp(argv[i], "file=", 5)) { opt.loadfile = argv[i] + 5; } else if (!strncmp(argv[i], "seg=", 4)) { - uint32_t segval = strtoul(argv[i] + 4, NULL, 0); - if (segval < 0x50 || segval > 0x9f000) { - error("Invalid segment\n"); + if(segseq2vals(argv[i] + 4, &opt.file_seg, &opt.file_lin, &opt.file_ip)) + goto bail; + } else if (!strncmp(argv[i], "segbs=", 5)) { + if(segseq2vals(argv[i] + 6, &opt.sect_seg, &opt.sect_lin, &opt.sect_ip)) + goto bail; + if(opt.sect_lin + SECTOR > ADDRMAX) { + error("Sector's address too big.\n"); goto bail; } - opt.seg = segval; } else if (!strncmp(argv[i], "isolinux=", 9)) { opt.loadfile = argv[i] + 9; opt.isolinux = true; } else if (!strncmp(argv[i], "ntldr=", 6)) { - opt.seg = 0x2000; /* NTLDR wants this address */ + opt.file_seg = 0x2000; /* NTLDR wants this address */ + opt.file_lin = 0x20000; + opt.file_ip = 0; opt.loadfile = argv[i] + 6; opt.sethidden = true; + opt.setgeometry = true; + opt.drvoff = 0x24; + opt.writebs = true; } else if (!strncmp(argv[i], "cmldr=", 6)) { - opt.seg = 0x2000; /* CMLDR wants this address */ + opt.file_seg = 0x2000; /* CMLDR wants this address */ + opt.file_lin = 0x20000; + opt.file_ip = 0; opt.loadfile = argv[i] + 6; opt.cmldr = true; opt.sethidden = true; + opt.setgeometry = true; + opt.drvoff = 0x24; + opt.writebs = true; } else if (!strncmp(argv[i], "freedos=", 8)) { - opt.seg = 0x60; /* FREEDOS wants this address */ + opt.file_seg = 0x60; /* FREEDOS wants this address */ + opt.file_lin = 0x600; + opt.file_ip = 0; opt.loadfile = argv[i] + 8; opt.sethidden = true; - } else if (!strncmp(argv[i], "msdos=", 6) || - !strncmp(argv[i], "pcdos=", 6)) { - opt.seg = 0x70; /* MS-DOS 2.0+ wants this address */ - opt.loadfile = argv[i] + 6; + opt.setgeometry = true; + opt.writebs = true; + opt.bsnomap = true; + } else if ( (val = 6, !strncmp(argv[i], "msdos=", 6) || + !strncmp(argv[i], "pcdos=", 6)) || + (val = 7, !strncmp(argv[i], "msdos7=", 7)) ) { + opt.file_seg = 0x70; /* MS-DOS 2.0+ wants this address */ + opt.file_lin = 0x700; + opt.file_ip = val == 7 ? 0x200 : 0; /* MS-DOS 7.0+ wants this ip */ + opt.loadfile = argv[i] + val; opt.sethidden = true; + opt.setgeometry = true; + opt.writebs = true; + opt.bsnomap = true; } else if (!strncmp(argv[i], "grub=", 5)) { - opt.seg = 0x800; /* stage2 wants this address */ + opt.file_seg = 0x800; /* stage2 wants this address */ + opt.file_lin = 0x8000; + opt.file_ip = 0x200; opt.loadfile = argv[i] + 5; opt.grub = true; } else if (!strncmp(argv[i], "grubcfg=", 8)) { @@ -1376,6 +1532,40 @@ int main(int argc, char *argv[]) opt.sethidden = true; } else if (!strcmp(argv[i], "nosethidden")) { opt.sethidden = false; + } else if (!strcmp(argv[i], "setgeometry")) { + opt.setgeometry = true; + } else if (!strcmp(argv[i], "nosetgeometry")) { + opt.setgeometry = false; + } else if (!strcmp(argv[i], "bsnomap")) { + opt.bsnomap = true; + } else if (!strcmp(argv[i], "nobsnomap")) { + opt.bsnomap = false; + } else if (!strncmp(argv[i], "setdrive",8)) { + if(!argv[i][8]) + val = 0x24; + else + val = strtoul(argv[i]+9, NULL, 16); + if (val >= SECTOR || !(val == 0x24 || val == 0x40)) { + error("Invalid setdrive offset.\n"); + goto bail; + } + opt.drvoff = val; + } else if (!strcmp(argv[i], "nosetdrive")) { + opt.drvoff = SECTOR; + } else if (!strcmp(argv[i], "setbpb")) { + opt.sethidden = true; + opt.setgeometry = true; + } else if (!strcmp(argv[i], "nosetbpb")) { + opt.sethidden = false; + opt.setgeometry = false; + } else if (!strcmp(argv[i], "writebs")) { + opt.writebs = true; + } else if (!strcmp(argv[i], "nowritebs")) { + opt.writebs = false; + } else if (!strcmp(argv[i], "filebpb")) { + opt.filebpb = true; + } else if (!strcmp(argv[i], "nofilebpb")) { + opt.filebpb = false; } else if (((argv[i][0] == 'h' || argv[i][0] == 'f') && argv[i][1] == 'd') || !strncmp(argv[i], "mbr:", 4) @@ -1387,14 +1577,14 @@ int main(int argc, char *argv[]) || !strcmp(argv[i], "boot") || !strncmp(argv[i], "boot,", 5) || !strcmp(argv[i], "fs")) { - drivename = argv[i]; - p = strchr(drivename, ','); + *drivename = argv[i]; + p = strchr(*drivename, ','); if (p) { *p = '\0'; - partition = p + 1; + *partition = p + 1; } else if (argv[i + 1] && argv[i + 1][0] >= '0' && argv[i + 1][0] <= '9') { - partition = argv[++i]; + *partition = argv[++i]; } } else { usage(); @@ -1407,12 +1597,59 @@ int main(int argc, char *argv[]) goto bail; } - if (opt.seg) { - regs.es = regs.cs = regs.ss = regs.ds = regs.fs = regs.gs = opt.seg; + return 0; +bail: + return -1; +} + +int main(int argc, char *argv[]) +{ + struct mbr *mbr = NULL; + struct disk_part_iter *cur_part = NULL; + void *sect_area = NULL, *file_area = NULL; + struct part_entry *hand_area = NULL; + void *cmp_buf = NULL; /* compare buffer for bpb mangling & writing */ + + struct syslinux_rm_regs regs; + char *drivename, *partition; + int hd, drive, whichpart = 0; /* MBR by default */ + uint64_t fs_lba = 0; /* Syslinux partition */ + uint32_t file_lba = 0; + struct guid gpt_guid; + unsigned char *isolinux_bin; + uint32_t *checksum, *chkhead, *chktail; + struct data_area data[3]; + int ndata = 0, file_didx = -1, sect_didx = -1; + static const char cmldr_signature[8] = "cmdcons"; + + openconsole(&dev_null_r, &dev_stdcon_w); + + /* Prepare the register set */ + memset(®s, 0, sizeof regs); + + /* Prepare and set non-0 default values */ + memset(&opt, 0, sizeof(opt)); + opt.file_lin = opt.sect_lin = opt.file_ip = opt.sect_ip = 0x7C00; + opt.drvoff = SECTOR; /* offset outside sector means turned off */ + + if(parse_args(argc, argv, &drivename, &partition)) + goto bail; + + /* set initial registry values, file takes precedence */ + + if(opt.loadfile) { + regs.es = regs.cs = regs.ss = regs.ds = regs.fs = regs.gs = opt.file_seg; + regs.ip = opt.file_ip; } else { - regs.ip = regs.esp.l = 0x7c00; + regs.es = regs.cs = regs.ss = regs.ds = regs.fs = regs.gs = opt.sect_seg; + regs.ip = opt.sect_ip; } + /* if handover address is default, set sp as well */ + + if(regs.ip == 0x7C00 && !regs.cs) + regs.esp.l = 0x7C00; + hd = 0; if (!strncmp(drivename, "mbr", 3)) { drive = find_disk(strtoul(drivename + 4, NULL, 0)); @@ -1521,18 +1758,20 @@ int main(int argc, char *argv[]) error("WARNING: failed to write MBR for 'hide'\n"); } - /* Do the actual chainloading */ - load_base = opt.seg ? (opt.seg << 4) : 0x7c00; - + /* Chainload the file if required */ if (opt.loadfile) { fputs("Loading the boot file...\n", stdout); if (loadfile(opt.loadfile, &data[ndata].data, &data[ndata].size)) { error("Failed to load the boot file\n"); goto bail; } - data[ndata].base = load_base; + if (opt.file_lin + data[ndata].size > ADDRMAX) { + error("File too large to load at this address\n"); + goto bail; + } + data[ndata].base = opt.file_lin; file_area = (void *)data[ndata].data; - load_base = 0x7c00; /* If we also load a boot sector */ + file_didx = ndata; /* Create boot info table: needed when you want to chainload another version of ISOLINUX (or another bootlaoder that needs @@ -1546,7 +1785,7 @@ int main(int argc, char *argv[]) /* Boot info table info (integers in little endian format) Offset Name Size Meaning - 8 bi_pvd 4 bytes LBA of primary volume descriptor + 8 bi_pvd 4 bytes LBA of primary volume descriptor 12 bi_file 4 bytes LBA of boot file 16 bi_length 4 bytes Boot file length in bytes 20 bi_csum 4 bytes 32-bit checksum @@ -1636,7 +1875,7 @@ int main(int argc, char *argv[]) if (data[ndata].size < sizeof(struct grub_stage2_patch_area)) { error - ("The file specified by grub=<loader> is to small to be stage2 of GRUB Legacy.\n"); + ("The file specified by grub=<loader> is too small to be stage2 of GRUB Legacy.\n"); goto bail; } @@ -1653,9 +1892,6 @@ int main(int argc, char *argv[]) goto bail; } - /* jump 0x200 bytes into the loadfile */ - regs.ip = 0x200; - /* * GRUB Legacy wants the partition number in the install_partition * variable, located at offset 0x208 of stage2. @@ -1700,11 +1936,18 @@ int main(int argc, char *argv[]) } } + if(opt.filebpb && cur_part) + mangle_bpb(data[ndata].data, (uint32_t)cur_part->lba_data, regs.edx.b[0]); ndata++; } - if (!opt.loadfile || data[0].base >= 0x7c00 + SECTOR) { - /* Actually read the boot sector */ + /* Check if loaded file doesn't overlap with boot sector */ + if (file_didx < 0 || opt.bsnomap || + data[file_didx].base + data[file_didx].size <= opt.sect_lin || + opt.sect_lin + SECTOR <= data[file_didx].base + ) { + + /* Actually read the boot sector (or point to the mbr) */ if (!cur_part) { data[ndata].data = mbr; } else if (!(data[ndata].data = read_sectors(cur_part->lba_data, 1))) { @@ -1714,7 +1957,9 @@ int main(int argc, char *argv[]) sect_area = (void *)data[ndata].data; data[ndata].size = SECTOR; - data[ndata].base = load_base; + data[ndata].base = opt.sect_lin; + if(!opt.bsnomap) + sect_didx = ndata; if (!opt.loadfile) { const struct mbr *br @@ -1726,29 +1971,47 @@ int main(int argc, char *argv[]) goto bail; } } - /* - * To boot the Recovery Console of Windows NT/2K/XP we need to write - * the string "cmdcons\0" to memory location 0000:7C03. - * Memory location 0000:7C00 contains the bootsector of the partition. - */ - if (cur_part && opt.cmldr) { - memcpy((char *)data[ndata].data + 3, cmldr_signature, - sizeof cmldr_signature); - } /* - * Modify the hidden sectors (partition offset) copy in memory; - * this modifies the field used by FAT and NTFS filesystems, and - * possibly other boot loaders which use the same format. + * Adjust some of the boot sector fields of FAT/NTFS filesystems - if + * applicable and requested by set{hidden,geometry,drive} or cmldr */ - if (cur_part && opt.sethidden) { - *(uint32_t *) ((char *)data[ndata].data + 28) = cur_part->lba_data; - } - ndata++; + if (cur_part) { + if (!(cmp_buf = malloc(SECTOR))) { + error("Could not allocate sector-compare buffer.\n"); + goto bail; + } + memcpy(cmp_buf, data[ndata].data, SECTOR); + + mangle_bpb((char*)data[ndata].data, (uint32_t)cur_part->lba_data, regs.edx.b[0]); + + /* + * Write adjusted boot sector to the disk, but only if at least one + * of the fields changed. It must be done before cmldr=. + */ + if (opt.writebs && memcmp(cmp_buf, data[ndata].data, SECTOR)) { + if (write_verify_sector(cur_part->lba_data, data[ndata].data)) { + error("Cannot write updated boot sector.\n"); + goto bail; + } + } + + /* + * To boot the Recovery Console of Windows NT/2K/XP we need to write + * the string "cmdcons\0" to memory location 0000:7C03. + * Memory location 0000:7C00 contains the boot sector of the partition. + */ + if (opt.cmldr) + memcpy((char *)data[ndata].data + 3, cmldr_signature, sizeof cmldr_signature); + + } + if(!opt.bsnomap) + ndata++; } if (cur_part) { + data[ndata].data = NULL; if (cur_part->next == next_gpt_part) { /* Do GPT hand-over, if applicable (as per syslinux/doc/gpt.txt) */ /* Look at the GPT partition */ @@ -1792,7 +2055,6 @@ int main(int argc, char *argv[]) data[ndata].base = 0x7be; data[ndata].size = synth_size; data[ndata].data = (void *)hand_area; - ndata++; regs.esi.w[0] = 0x7be; dprintf("GPT handover:\n"); @@ -1814,12 +2076,30 @@ int main(int argc, char *argv[]) data[ndata].base = 0x7be; data[ndata].size = sizeof(struct part_entry); data[ndata].data = (void *)hand_area; - ndata++; regs.esi.w[0] = 0x7be; dprintf("MBR handover:\n"); mbr_part_dump(hand_area); } + + if(data[ndata].data) { + /* We have to make sure, that handover data doesn't overlap with the file and/or the boot sector. + * For example, part of FreeDOS kernel loaded at 0x600 would conflict with the handover data. + * Still, in case of file=, the handover data is generally not needed (not always though - e.g. + * recovery console), so we do the following check and increase ndata if there're no conflicts. + */ + if (( file_didx < 0 || /* check against file */ + data[file_didx].base + data[file_didx].size <= data[ndata].base || + data[ndata].base + data[ndata].size <= data[file_didx].base + ) && ( sect_didx < 0 || /* check against sector */ + data[sect_didx].base + data[sect_didx].size <= data[ndata].base || + data[ndata].base + data[ndata].size <= data[sect_didx].base + )) { + ndata++; + } else { + dprintf("Handover ignored due to overlapping regions.\n"); + } + } } do_boot(data, ndata, ®s); -- 1.6.3.1
H. Peter Anvin
2010-Jul-26 23:02 UTC
[syslinux] [RFC/PATCH] New chainloading functionality
On 07/26/2010 03:50 PM, Michal Soltys wrote:> > To retain compatibility with older configs simple invocation such as seg=0x60 > is perfectly valid. Only potential incompatibilities here, is if someone used > decimal values (e.g. seg=96) or if someone used seg= to actually control boot > sector address/jump (extremely unlikely). >Hm... I don't like this; overall I don't like numbers that default to hex with no override. I'm willing to accept that if it has a colon in it it is parsed as hex, just because seg:offs values get really awkward otherwise, but not in general.> * update the "drive unit" field in a FAT/NTFS boot sector. Offset should > * be either 0x24 (FAT/NTFS excluding FAT32) or 0x40 (FAT32 only) - > * chainloader won't accept other values. Offset is parsed as a *HEX* > * number. Offsetless value defaults to 0x24.Same thing here. The other thing that I wonder is if this work conflicts with sha0's librarization work that is currently on the "disklib" branch in the git repository. -hpa
Reasonably Related Threads
- PATCH/RFC chain.c: update iterator code, yank from chain.c, move both to separate directory
- [PATCH] chain.c: allocation fixes
- Possible improvements for chain.c
- chain.c: fix v2 for public indexes in iterators (for master and disklib)
- [PATCH] chain.c32: support chainloading GRUB2 core.img