From viro@ftp.linux.org.uk Fri Feb 27 20:21:34 2015 Received: (at 629506) by bugs.debian.org; 27 Feb 2015 20:21:34 +0000 X-Spam-Checker-Version: SpamAssassin 3.4.0-bugs.debian.org_2005_01_02 (2014-02-07) on buxtehude.debian.org X-Spam-Level: X-Spam-Status: No, score=-2.3 required=4.0 tests=BAYES_00,FOURLA,MONEY, MURPHY_DRUGS_REL8,RCVD_IN_DNSWL_MED,STOCKLIKE,T_RP_MATCHES_RCVD autolearn=no autolearn_force=no version=3.4.0-bugs.debian.org_2005_01_02 X-Spam-Bayes: score:0.0000 Tokens: new, 43; hammy, 150; neutral, 234; spammy, 0. spammytokens: hammytokens:0.000-+--UD:patch, 0.000-+--H*u:1.5.21, 0.000-+--H*UA:1.5.21, 0.000-+--H*u:2010-09-15, 0.000-+--H*UA:2010-09-15 Return-path: Received: from zeniv.linux.org.uk ([195.92.253.2]) by buxtehude.debian.org with esmtps (TLS1.0:DHE_RSA_AES_256_CBC_SHA1:256) (Exim 4.80) (envelope-from ) id 1YRRPi-0007QW-72 for 629506@bugs.debian.org; Fri, 27 Feb 2015 20:21:34 +0000 Received: from viro by ZenIV.linux.org.uk with local (Exim 4.76 #1 (Red Hat Linux)) id 1YRQyr-0000Hl-OS for 629506@bugs.debian.org; Fri, 27 Feb 2015 19:53:49 +0000 Date: Fri, 27 Feb 2015 19:53:49 +0000 From: Al Viro To: 629506@bugs.debian.org Subject: memtest86+.bin crashes if loader ends up putting it not at 9000:0000 Message-ID: <20150227195349.GN29656@ZenIV.linux.org.uk> MIME-Version: 1.0 Content-Type: text/plain; charset=us-ascii Content-Disposition: inline User-Agent: Mutt/1.5.21 (2010-09-15) Sender: Al Viro X-Greylist: delayed 1658 seconds by postgrey-1.34 at buxtehude; Fri, 27 Feb 2015 20:21:33 UTC FWIW, the effects described in this bug report are 100% reproducible on any version, as long as the loader (lilo, grub, whatever) ends up putting the bootsect+setup in any location below 9000:0000. zImage-type images consist of 3 parts - bootsect, setup and payload. Payload is loaded at 1000:0, bootsect and setup - n:0 and n+2:0 resp., near the top of lowmem. Payload is protected mode code, setup - real mode one. Bootsect isn't executed at all; when the same image is booted directly it would've been the only part loaded by BIOS and it would copy itself and read the rest of the image to expected locations and pass control to setup. Values in it can be used by setup, though, so it must be present even when the image had been brought in by a loader. The thing is, we can't be guaranteed n == 0x9000. E.g. ACPI and SMM have every right to use _anything_ in range 512K..1M for their state, declaring it reserved. That's what BIOS int 0x12 is for - it reports how high (in kilobytes) can you go in lowmem without running into reserved areas. And while having it report 512K is rare, something like 20K reserved just below the VRAM (i.e. report 620K) is nothing unusual. I hadn't looked into details of GRUB behaviour, but LILO puts its secondary loader as high in lowmem as it can, then puts the bootsect and setup parts of image below that. It tries to load at 9000:0 if possible, but if there's no space, it'll go lower. With the sizes it uses 20K reserved below 640K is enough to push bootsect + setup combination (just) below 9000:0. And memtest86+ setup has that 0x9000 hardwired - this gdt_48: .word 0x800 # gdt limit=2048, 256 GDT entries .word 512+gdt - start,0x9 # gdt base = 0X9xxxx in setup.S really depends on 'start' (entry to setup) loaded at 0x90200 physical. Have it loaded at any other address and you'll trigger an exception as soon as you try to assign any segment register after you switch to protected mode. Which will escalate to triple-fault and reboot the damn thing immediately. GRUB might be better or worse in triggering that "load not at 9000:0" situation, but it can't possibly avoid it in all cases. If nothing else, 9000:0 might be within one of the reserved areas. It simply isn't guaranteed to be available, period. Another issue is that setup of memtest86+ expects to see in %dx the value left there by the bootsect of memtest86+. Fortunately, it's not critical - in movw $INITSEG, %ax movw %ax, %ds movw %ax, %es movw %ax, %fs movw %ax, %ss # reset the stack to INITSEG:0x4000-12. movw %dx, %sp push %cs pop %ds we really need only the last two instructions. The stack footprint of what follows is fairly low *and* these values are not used past the reload of segment registers immediately after protected mode switch. It's not the only problem in there - the bits after # start from grub-a20.patch are clearly cargo-culted from grub, badly. There it was a part of a function that expects an argument on stack (it can turn A20 both on and off) and that got blindly copied, nevermind that the value on stack is random or that we follow it with (unconditional) use of 8042-based method anyway. I'd rather see upstream opinion on that particular piece of code first, though. Anyway, the patch below fixes dependency on being loaded at 9000:0 and it seems to work here without regressions. Have fun... --- memtest86+-5.01/setup.S 2013-08-09 22:01:58.000000000 -0400 +++ fixed/setup.S 2015-02-27 14:18:47.000000000 -0500 @@ -26,14 +26,13 @@ # APs also execute this code #ljmp $INITSEG, $(reload - start + 0x200) reload: - movw $INITSEG, %ax - movw %ax, %ds - movw %ax, %es - movw %ax, %fs - movw %ax, %ss # reset the stack to INITSEG:0x4000-12. - movw %dx, %sp + xorl %eax, %eax push %cs - pop %ds + pop %ax + movw %ax, %ds + shll $4, %eax + addl %eax, (gdt_48 - start + 2) + lidt idt_48 - start # load idt with 0,0 lgdt gdt_48 - start # load gdt with whatever appropriate @@ -88,6 +87,7 @@ movw %ax, %ds movw %ax, %es movw %ax, %ss + xorl %esp, %esp # 32bit code will set it then movw %ax, %fs movw %ax, %gs @@ -144,7 +144,7 @@ gdt_48: .word 0x800 # gdt limit=2048, 256 GDT entries - .word 512+gdt - start,0x9 # gdt base = 0X9xxxx + .word gdt - start,0 # gdt base, needs to be adjusted msg1: .asciz "Setup.S\r\n"