While working on the next version of File looper, I encountered a bug that hindered me mounting any filesystem using virtual devices
created by File looper.
TL;DR IRIX 6.5.30 has a bug that prevents mounting valid filesystems through block device drivers, as well as not allowing to unload such drivers once loaded and accessed (returning an EBUSY error). To fix both issues, you can use the patch provided in patchMLOADBLK.tar
EDIT: Corrected shell output indentation
1. Symptoms
After loading a block or both character and block device driver and opening any of its block device files, no further unloading of the kernel module is allowed.
The following error is shown:
When trying to mount a filesystem, the following error is shown sporadically:
Both symptoms have the same root cause.
2. Environment
The symptoms appeared in the following development environments:
SGI O2 (IP32), Octane (IP30) and Origin 300 (IP35), all running IRIX 6.5.30 with MIPSpro 7.4.4 compiler suite.
The OS was a fresh install on all three systems.
Software that caused above symptoms was, presumably, my File looper driver, a character and block driver implemented as a dynamically loadable kernel module (DLKM).
3. Debugging
First, all kind of possible causes had been ruled out:
Since all these causes could be ruled out, a deeper look was necessary. As is the case with "normal" programming in user space, if there is a hard to spot error, a debugger should be used to trace back execution until enough context is known to understand the bug.
4. Kernel debugging
4.1 Where to start
The information at hand was, in summary:
A simplified view of the execution path from mount(1M) to the kernel and back:
After some tries, the offending code was narrowed down to efs_mountfs(). A closer look at efs_mountfs() showed the error coming from the second function called inside of it.
A more detailed stacktrace:
4.2 devhold()
Dynamically tracing execution of a failed mount gave enough information about where the ENXIO error came from:
The crucial part is:
The kernel debugger shows the dfind_devsw() function is invoked at this point:
In C code, this would translate to:
The decompiled devhold() function is semantically equivalent to:
Dynamic debugging yields the following:
4.3 dfind_devsw()
The decompiled dfind_devsw() function is semantically equivalent to:
Dynamic debugging yields the following:
Every DLKM that is loaded into a running system, gets the pertinent device switch entries allocated from the system. If the driver is a character only driver, then just a character device switch entry is allocated, if it is a block only driver, then just a block device switch entry is allocated, and if it is of both types, then two device switch entries are allocated, one for each type of device.
Then, the system inserts an entry for the newly loaded driver into drvtab. The index into drvtab is calculated depending on the device types supported and follows this scheme:
hash(address of device switch) = index into drvtab of driver descriptor
4.5 The bug
In the case of loading either a block only driver or a character and block driver, devhold() is invoked with the correct address of the block device switch entry, but only the second argument is used, which will be NULL in both cases. The logic in devhold() does not handle these two cases or, in a more simplified view: devhold() does not handle correctly the case of block drivers.
Furthermore, dfind_devsw() does not validate the input parameter before usage, which will result in incorrect descriptors returned whenever the character device switch passed to devhold() is NULL.
The sporadic nature of the symptoms are due to the impredictability of the allocated addresses for the character or block device switch entry used to calculate the drvtab indices.
Possible scenarios in a non-patched system are:
5. The patch
Attending to the system logic for DLKMs, the following modifications must be included in devhold():
Overview of the patch from A (before) to B (after):
5.1 Patching requirements
Patching binaries of any kind always comes along with some requirements that must be met. The kernel file /unix is no exception, as it has to continue to run undisturbed after being patched. Being such a big and complex file requires even more attention to detail. Otherwise:
The following requirements should be met:
Sticking to these few requirements ensures the patched kernel will not break after further modifications, nor will stability be compromised.
The tools necessary to follow the patching process step by step are:
The patch presented here is a conservative implementation of a patch that fixes the kernel bug described above while meeting all the requirements outlined in the previous section.
All files necessary to reproduce the patch, including the tool elfen, are provided in a patch bundle named patchMLOADBLK_bundle.tar:
First, create a working directory:
Unpack the patch bundle patchMLOADBLK_bundle.tar:
Some file backups should be made to be able to revert to the original state should anything go wrong.
For instance, a backup of the system archive /var/sysgen/boot/os.a is highly recommended.
A copy of the originally running kernel /unix should be copied under / with a descriptive short name:
Patching will be done exclusively in a relocatable kernel object file called mload.o, which can be extracted from the system os.a or from the backed up os.a archive. A backup of this file is also recommended (this avoids having to reextract a new copy each time).
The necessary changes to existing code would result in a disproportionate amount of new code added to devhold(). A quick look at the patch in C code shows that not only one additional check for block drivers is necessary, but also a complete while loop, which would resemble the loop in dfind_devsw(), with minor modifications.
An entirely in-place modification in devhold() is not feasable.
The solution would be to redirect flow from devhold() to an external function, which will be called patchMLOADBLK(). This in turn requires a new kernel object file containing the code for patchMLOADBLK(). This object file will be called patchMLOADBLK.o and will be added in the final step to /var/sysgen/boot/os.a before linking the new kernel.
To redirect control flow from devhold() to patchMLOADBLK(), a function call has to take place at some point in devhold(). To patch this in, first the file offset of devhold() must be known.
5.2.1 Get file offset of devhold()
File offsets are calculated from both the section offset (.text in this case) and symbol offset (.symtab).
Section offset from beginning of mload.o file: 0x217c.
Symbol offset from beginning of section .text: 0x35d8(13784).
devhold() symbol offset from beginning of mload.o file:
0x217c(8572) + 0x35d8(13784) = 0x5754(22356)
The optimal control transfer instruction would be:
5.2.2 Patch devhold()
With the file offset known, patching in the control transfer instructions is straightforward. Open a hex editor and go to the calculated devhold symbol offset.
The new instruction bytes can be taken from the excerpt below.
5.2.3 Add relocation entry for patchMLOADBLK symbol
For the linker to modify the patched in jal instruction, a relocation entry is needed.
Relocation entries are organized by sections in each relocatable object. These relocation sections follow a simple naming convention.
If a section, for example .text, has objects (functions, variables, other sections, ...) that are to be relocated, the resulting .o file will have a relocation section called .rel.text or .rela.text, listing all entries that need to be relocated in section .text .
For an explanation of the differences between .rel and .rela entries, please refer to the official documentation for the MIPS ISA.
The patchMLOADBLK patch requires only .rel.text section to be modified.
To list all relocation entries in mload.o, list all sections and look for .rel.* sections:
A listing of relocation entries from .rel.text:
The offset field indicates the offset to the instruction(s) that need to be modified, starting from the beginning of the target section (i.e. .text for .rel.text).
In this case, the instructions to be modified by the linker are:
The offset of the jal instruction is 0x35e0, as can be seen above.
As a sidenote, if you are curious as to how a relocation entry relates to an instruction like jal, the key information is the type field of the relocation entry: R_MIPS_26.
The following relocation entry needs to be added to mload.o:
The column named Symndx is not a string, but an index into the symbol table of the patched mload.o, stored in section .symtab. The symbol table entry in turn stores a string table index to get to the ASCII representation of the symbol.
Thus, an entry to the string table .strtab has to be created and added too. The relationship between all three sections is:
Relocation table [has index into] Symbol table [has index into] String table [has ASCII string]
Luckily, a tool like elfen can be used to do all of this in an automated fashion. This tool is freely available and distributed with the patch bundle patchMLOADBLK_bundle.tar.
First, the patchMLOADBLK symbol has to be added to the symbol table and string table:
Note that elfen does not modify the input file, it generates its output in a new file named after the input file with an appended .new suffix.
Also note that the second argument, 2, stands for the symbol type STB_GLOBAL (shows just as GLOBAL in elfdump output).
Check to see if the symbol was added correctly:
Rename mload.o.new to mload.o:
Second, add the relocation entry for devhold() to call patchMLOADBLK():
A quick overview of the arguments:
The second argument, 2, denotes the index of the relocation table where this entry is to be added to. Relocation table indices start with the first relocation section, not with the first section in the object file section table.
For example, a relocatable object with 10 sections in total of which 2 are relocation sections, would have the following indices:
For mload.o, the relocation table index for .rel.text counted this way, is 2.
The third argument, 293, is the symbol table index for patchMLOADBLK (see above).
The fourth argument, 13792, is the offset in decimal to the jal instruction, starting from the beginning of the target section .text in mload.o.
Quick reminder:
Symbol offset for devhold from beginning of section: 0x35d8(13784).
The offset needs to be adjusted to point to the desired instruction, which is 2 instructions below:
0x35d8(13784) + 2 * 4 = 13792
Last but not least, 3, directs elfen to create a relocation entry of type R_MIPS_26.
Check for the new relocation entry:
And don't forget to rename the output file from mload.o.new to mload.o:
5.2.4 patchMLOADBLK.c
The code in patchMLOADBLK() has to check both device switch entries, bdevsw and cdevsw, and do appropriate calls to dfind_devsw():
5.2.5 Patching dfind_devsw()
In order to traverse the drvtab hash list from patchMLOADBLK.c, a reference to drvtab is necessary. But the drvtab symbol is local to mload.o. This poses a problem because only code inside of mload.o may access it.
The following techniques may be applied here:
The most efficient technique is the first one, because it served the purpose with the fewest changes to existing code. Specifically, the following modification allows to exfiltrate the drvtab address to the caller by just invoking dfind_devsw() with any argument. This function is marked GLOBAL and thus is accessible from any other object file, including patchMLOADBLK.o.
The technique requires the use of temporary registers that are not modified throughout the course of the dfind_devsw() function, returning safely to the caller.
Refer to the MIPS N32/64 ABI for more details on temporary registers.
As a result, this temporary register will, in effect, exfiltrate the drvtab address.
For example, using the t2 register:
Upon return from dfind_devsw(), its caller can access drvtab's address through the t2 register.
Patching can be done by searching for the hexastring 3c0100003042001f from the second and third instruction in dfind_devsw():
dfind_devsw:
[1193] 0x14f4: 00 04 10 c2 srl v0,a0,3
[1193] 0x14f8: 3c 01 00 00 lui at,0
[1193] 0x14fc: 30 42 00 1f andi v0,v0,31
The new instruction bytes can be taken from the excerpt below.
Check for correctness:
5.2.6 From patchMLOADBLK.c to patchMLOADBLK.s
Since access to registers is needed, as well as to the stack (refer to 5.2.4 patchMLOADBLK.c for details), assembly language would be more appropriate than C.
A possible implementation could be:
A skeleton that can be worked upon can be obtained by creating an empty *.c file with an empty function definition, and then adding the missing assembly code bits to implement the C code from patchMLOADBLK.c.
5.2.7 From patchMLOADBLK.s to patchMLOADBLK.o
A Makefile is provided to compile the patchMLOADBLK.s file. Basically, it consists of the same compiler flags used to compile loadable kernel modules.
5.2.8 Creating a new kernel
The final step consists of replacing the original object mload.o file with the patched one and adding the new external patchMLOADBLK.o object for the linker to find the global patchMLOADBLK function symbol.
The following procedure can be used:
Finally, relink a new kernel by invoking autoconfig:
This will place the new, patched kernel in /unix.install and use this file as the new kernel upon next reboot.
5.2.9 patchMLOADBLK.sh
A patch script, patchMLOADBLK.sh, is provided as part of patchMLOADBLK.tar to automate the patching process. Invoke the script with -h to get the help menu.
created by File looper.
TL;DR IRIX 6.5.30 has a bug that prevents mounting valid filesystems through block device drivers, as well as not allowing to unload such drivers once loaded and accessed (returning an EBUSY error). To fix both issues, you can use the patch provided in patchMLOADBLK.tar
EDIT: Corrected shell output indentation
1. Symptoms
After loading a block or both character and block device driver and opening any of its block device files, no further unloading of the kernel module is allowed.
The following error is shown:
Bash:
bash-2.05b# make -f Makefile.irix unload
--- unload ---
ml unld -v `ml | grep fl_ | awk "{print $2}"`
Error unloading module 8: Device busy.
bash-2.05b#
Bash:
bash-2.05b# mount -o ro -t efs /dev/loop/ov13 /mnt/EFS
mount: /dev/loop/ov13 on /mnt/EFS: No such device or address
mount: giving up on:
/mnt/EFS
bash-2.05b#
2. Environment
The symptoms appeared in the following development environments:
SGI O2 (IP32), Octane (IP30) and Origin 300 (IP35), all running IRIX 6.5.30 with MIPSpro 7.4.4 compiler suite.
The OS was a fresh install on all three systems.
Software that caused above symptoms was, presumably, my File looper driver, a character and block driver implemented as a dynamically loadable kernel module (DLKM).
3. Debugging
First, all kind of possible causes had been ruled out:
- Race conditions in the driver
- Memory leaks in the driver
- ABI conflicts caused by mixing user space and kernel space ABIs
- Character only drivers did not show unloading symptom
- Character and block drivers showed the symptoms
- Block only drivers showed the symptoms
- Kernel bit width issue was ruled out by using both kernels, IRIX and IRIX64, and both had the same symptoms
- Pattern observation by repeatedly power cycling the machines gave no tangible result
- Driver specific global variables were checked
- SMP (i.e. symmetric multiprocessing) as a root cause was immediately ruled out because two of the machines, IP30 and IP32, were UP (i.e. uniprocessor) machines
- Registered/non-registered DLKMs showed both the two symptoms
- Filesystem type was ruled out by using different filesystems when mounting, all resulted in the same symptoms showing up
4. Kernel debugging
4.1 Where to start
The information at hand was, in summary:
- User space mount(1M) returned ENXIO
- Filesystem type used with mount was EFS
- mount is a user program, a library function and also a syscall
After some tries, the offending code was narrowed down to efs_mountfs(). A closer look at efs_mountfs() showed the error coming from the second function called inside of it.
A more detailed stacktrace:
4.2 devhold()
Dynamically tracing execution of a failed mount gave enough information about where the ENXIO error came from:
Bash:
bash-2.05b# uname -a
IRIX OSIRIS 6.5 07202013 IP32
bash-2.05b#
bash-2.05b# dis -p devhold /unix
**** DISASSEMBLER ****
disassembly for /unix
section .text
devhold:
[2573] 0x801f81c8: 27 bd ff e0 addiu sp,sp,-32
[2573] 0x801f81cc: ff bf 00 00 sd ra,0(sp)
[2573] 0x801f81d0: ff b7 00 08 sd s7,8(sp)
[2573] 0x801f81d4: 00 a0 b8 25 move s7,a1
: Next call returns ESUCCESS
[2579] 0x801f81d8: 0c 01 ee c7 jal 126663 { 0x1eec7 }
[2579] 0x801f81dc: ff b0 00 10 sd s0,16(sp)
[2579] 0x801f81e0: 00 40 80 25 move s0,v0
: If this returns 0, ENXIO (6) is returned,
: otherwise ESUCCESS (0) is returned
[2581] 0x801f81e4: 0c 07 d8 39 jal 514105 { 0x7d839 }
[2581] 0x801f81e8: 02 e0 20 25 move a0,s7
: This branch leads to the return value of ENXIO (6)
[2581] 0x801f81ec: 10 40 00 15 beq v0,zero,0x801f8244
[2581] 0x801f81f0: df b7 00 08 ld s7,8(sp)
[2586] 0x801f81f4: dc 44 00 50 ld a0,80(v0)
[2586] 0x801f81f8: 10 80 00 09 beq a0,zero,0x801f8220
[2586] 0x801f81fc: 00 00 00 00 nop
[2586] 0x801f8200: 8c 01 a0 18 lw at,-24552(zero)
[2586] 0x801f8204: dc 21 00 68 ld at,104(at)
[2586] 0x801f8208: 10 24 00 05 beq at,a0,0x801f8220
[2586] 0x801f820c: 00 00 00 00 nop
[2587] 0x801f8210: 0c 01 ef 54 jal 126804 { 0x1ef54 }
[2587] 0x801f8214: 02 00 20 25 move a0,s0
[2588] 0x801f8218: 10 00 00 06 b 0x801f8234
[2588] 0x801f821c: 24 02 00 06 li v0,6
[2591] 0x801f8220: 0c 07 e0 63 jal 516195 { 0x7e063 }
[2591] 0x801f8224: 00 40 20 25 move a0,v0
[2593] 0x801f8228: 0c 01 ef 54 jal 126804 { 0x1ef54 }
[2593] 0x801f822c: 02 00 20 25 move a0,s0
[2595] 0x801f8230: 00 00 10 25 move v0,zero
[2595] 0x801f8234: df bf 00 00 ld ra,0(sp)
[2595] 0x801f8238: df b0 00 10 ld s0,16(sp)
[2595] 0x801f823c: 03 e0 00 08 jr ra
[2595] 0x801f8240: 27 bd 00 20 addiu sp,sp,32
[2582] 0x801f8244: 0c 01 ef 54 jal 126804 { 0x1ef54 }
[2582] 0x801f8248: 02 00 20 25 move a0,s0
[2583] 0x801f824c: 10 00 ff f9 b 0x801f8234
[2583] 0x801f8250: 24 02 00 06 li v0,6
bash-2.05b#
Bash:
...
[2581] 0x801f81e4: 0c 07 d8 39 jal 514105 { 0x7d839 }
[2581] 0x801f81e8: 02 e0 20 25 move a0,s7
[2581] 0x801f81ec: 10 40 00 15 beq v0,zero,0x801f8244
...
Bash:
...
[2581] 0x801f81e4: 0c 07 d8 39 jal 514105 { dfind_devsw }
[2581] 0x801f81e8: 02 e0 20 25 move a0,s7
[2581] 0x801f81ec: 10 40 00 15 beq v0,zero,0x801f8244
...
C:
devhold(a0, a1)
{
...
ret = dfind_devsw(a0=s7=a1);
if (ret == 0)
{
return ENXIO;
}
...
}
C:
int devhold(struct bdevsw *bdsw,
struct cdevsw *cdsw)
{
cfg_desc_t *desc;
// Lock presumably the drvtab structure accessed in dfind_devsw()
ret_msl = splhi(drvlock);
// Get descriptor for character device switch entry
desc = dfind_devsw(cdsw);
if (desc == NULL)
{
// No descriptor found
// Unlock presumably the drvtab structure accessed in dfind_devsw()
splx(drvlock,
ret_msl);
return ENXIO;
}
if (desc->m_priv_kid == NULL)
{
goto LABEL_0;
}
at = Unknown global variable near global_lock;
if (desc->m_priv_kid == at)
{
goto LABEL_0;
}
// Unlock presumably the drvtab structure accessed in dfind_devsw()
splx(drvlock,
ret_msl);
return ENXIO;
LABEL_0:
devupdate(desc);
// Unlock presumably the drvtab structure accessed in dfind_devsw()
splx(drvlock,
ret_msl);
return ESUCCESS;
}
- devhold() is used to increment the reference count on the device descriptor (by passing it as argument to devupdate())
- ENXIO is always returned through the first path:
C:
int devhold(struct bdevsw *bdsw,
struct cdevsw *cdsw)
{
...
// Get descriptor for character device switch entry
desc = dfind_devsw(cdsw);
if (desc == NULL)
{
// No descriptor found
...
return ENXIO;
}
...
}
- ESUCCESS is always returned through the first goto LABEL_0:
C:
int devhold(struct bdevsw *bdsw,
struct cdevsw *cdsw)
{
...
if (desc->m_priv_kid == NULL)
{
goto LABEL_0;
}
...
LABEL_0:
...
return ESUCCESS;
}
- The arguments to devhold() do not reflect all the device switch entries of the driver owning these entries. This is of semantic relevance: if a driver has both device switch entries, character and block, the devhold() function may be called with any of the two device switch entry addresses:
- devhold(block devsw of driver, NULL)
- devhold(NULL, character devsw of driver)
4.3 dfind_devsw()
Bash:
bash-2.05b# uname -a
IRIX OSIRIS 6.5 07202013 IP32
bash-2.05b#
bash-2.05b# dis -p dfind_devsw /unix
**** DISASSEMBLER ****
disassembly for /unix
section .text
dfind_devsw:
: One argument only, a0
[1193] 0x801f60e4: 00 04 10 c2 srl v0,a0,3
: Compute address in at. PART 1
[1193] 0x801f60e8: 3c 01 80 59 lui at,-32679
[1193] 0x801f60ec: 30 42 00 1f andi v0,v0,31
: Compute address in at. PART 2
[1193] 0x801f60f0: 24 21 63 48 addiu at,at,25416
[1193] 0x801f60f4: 00 02 10 80 sll v0,v0,2
: Compute address in at. PART 3
[1193] 0x801f60f8: 00 22 08 21 addu at,at,v0
: Get word at commputed address in at
[1193] 0x801f60fc: 8c 21 00 00 lw at,0(at)
: If computed address is zero, return ESUCCESS (0)
[1193] 0x801f6100: 10 20 00 0b beq at,zero,0x801f6130
: v0 = at
[1193] 0x801f6104: 00 20 10 25 move v0,at
: Dereference v0 at offset 12, store in a3
[1195] 0x801f6108: 8c 47 00 0c lw a3,12(v0)
: Dereference a3 at offset 192, store in v1
[1195] 0x801f610c: 8c e3 00 c0 lw v1,192(a3)
: Compare dereferenced value with argument a0
[1195] 0x801f6110: 10 64 00 08 beq v1,a0,0x801f6134
[1195] 0x801f6114: 00 00 00 00 nop
: Dereference at offset 188, store in a1
[1195] 0x801f6118: 8c e5 00 bc lw a1,188(a3)
: Compare a1 with argument a0
[1195] 0x801f611c: 10 a4 00 05 beq a1,a0,0x801f6134
[1195] 0x801f6120: 00 00 00 00 nop
: Dereference v0 at offset 56, store in v0
[1200] 0x801f6124: 8c 42 00 38 lw v0,56(v0)
: If v0 is not zero, jump back to dereference
: v0 at offset 12 above
[1200] 0x801f6128: 14 40 ff f7 bne v0,zero,0x801f6108
[1200] 0x801f612c: 00 00 00 00 nop
[1202] 0x801f6130: 00 00 10 25 move v0,zero
[1202] 0x801f6134: 03 e0 00 08 jr ra
[1202] 0x801f6138: 00 00 00 00 nop
bash-2.05b#
C:
cfg_desc_t *dfind_devsw(void *devsw)
{
cfg_desc_t *desc;
hash = (((unsigned long) devsw >> 3) & 31);
desc = drvtab[hash];
if (desc == NULL)
{
return 0;
}
// Loop through descriptor chain
while (desc != NULL)
{
// Get driver type data (refer to /usr/include/mload.h)
ust = (unknown_struct_type *) desc->m_data;
if ((void *) ust->cdevsw == (void *) devsw)
{
break;
}
if ((void *) ust->bdevsw == (void *) devsw)
{
break;
}
desc = desc->m_next;
}
return desc;
}
- The parameter, devsw, is passed through to the hash function without validation
- drvtab is a hash list, meaning it contains entries that are accessed by a hashed index and these entries point to data structures that are linked like a singly linked list:
- drvtab has a size of 32 entries
- Each drvtab entry is a pointer to a descriptor of type cfg_desc_t
- The definition of the drvtab array (or hash list) in C, would be:
C:
cfg_desc_t *drvtab[32];
- The unknown structure type has two fields at the specified indices (see disassembled code) which directly correlate in their values (a pointer to a character and block devsw struct) to the actual device switch entry addresses for the character and/or block devices of the driver who owns them.
So, for example in case of File looper compiled as character and block driver, one device switch entry is created for/in the systemwide character device switch table, and one device switch entry is created for/in the systemwide block device switch table.
When loading the file looper driver, kernel code creates a new entry (descriptor) in drvtab for File Looper and points the m_data field of this descriptor to an unknown structure that is expected to have the correct values (or addresses) of the character device switch entry and of the block device switch entry created for File Looper.
Every DLKM that is loaded into a running system, gets the pertinent device switch entries allocated from the system. If the driver is a character only driver, then just a character device switch entry is allocated, if it is a block only driver, then just a block device switch entry is allocated, and if it is of both types, then two device switch entries are allocated, one for each type of device.
Then, the system inserts an entry for the newly loaded driver into drvtab. The index into drvtab is calculated depending on the device types supported and follows this scheme:
- If a driver supports only character devices, the address for the character device switch entry is used to calculate the drvtab index
- If a driver supports only block devices, the address for the block device switch entry is used to calculate the drvtab index
- If a driver supports both character and block devices, the address for the character device switch entry is used to calculate the drvtab index
hash(address of device switch) = index into drvtab of driver descriptor
4.5 The bug
In the case of loading either a block only driver or a character and block driver, devhold() is invoked with the correct address of the block device switch entry, but only the second argument is used, which will be NULL in both cases. The logic in devhold() does not handle these two cases or, in a more simplified view: devhold() does not handle correctly the case of block drivers.
Furthermore, dfind_devsw() does not validate the input parameter before usage, which will result in incorrect descriptors returned whenever the character device switch passed to devhold() is NULL.
The sporadic nature of the symptoms are due to the impredictability of the allocated addresses for the character or block device switch entry used to calculate the drvtab indices.
Possible scenarios in a non-patched system are:
- Loading of a CHR only driver with subsequent access of a driver owned CHR device file
- Loading of a CHR and BLK driver with subsequent access of a driver owned CHR device file
- Loading of a BLK only driver with subsequent mount of a driver owned BLK device file
- Loading of a CHR and BLK driver with subsequent mount of a driver owned BLK device file
5. The patch
Attending to the system logic for DLKMs, the following modifications must be included in devhold():
C:
int devhold(struct bdevsw *bdsw,
struct cdevsw *cdsw)
{
cfg_desc_t *desc;
// Lock presumably the drvtab structure accessed in dfind_devsw()
ret_msl = splhi(drvlock);
// NEW: Check for NULL device switch entries
if (cdsw != NULL)
{
// Get descriptor for character device switch entry
desc = dfind_devsw(cdsw);
}
else if (bdsw != NULL)
{
// Get descriptor for block device switch entry by traversing
// the entire drvtab hashlist.
//
// Dynamic debugging showed that when invoked with a block device
// switch entry, the second parameter is NULL, and there is no
// method readily available to determine the type of the driver
// this bdevsw belongs to.
//
// Since drvtab is hashed based on the type of the driver,
// the entire drvtab must be searched for the correct driver
// description entry.
for (drvidx = 0; drvidx < 32; drvidx++)
{
// Get next driver descriptor
desc = drvtab[drvidx];
// Skip if index has no entry
if (desc == NULL)
{
continue;
}
// Loop through descriptor chain
while (desc != NULL)
{
// Get driver type data (refer to /usr/include/mload.h)
ust = (unknown_struct_type *) desc->m_data;
// NEW: Check just block device switch entry
if (ust->bdevsw == bdsw)
{
break;
}
desc = desc->m_next;
}
}
}
else
{
return ENXIO;
}
if (desc == NULL)
{
// No descriptor found
// Unlock presumably the drvtab structure accessed in dfind_devsw()
splx(drvlock,
ret_msl);
return ENXIO;
}
if (desc->m_priv_kid == NULL)
{
goto LABEL_0;
}
at = Unknown global variable near global_lock;
if (desc->m_priv_kid == at)
{
goto LABEL_0;
}
// Unlock presumably the drvtab structure accessed in dfind_devsw()
splx(drvlock,
ret_msl);
return ENXIO;
LABEL_0:
devupdate(desc);
// Unlock presumably the drvtab structure accessed in dfind_devsw()
splx(drvlock,
ret_msl);
return ESUCCESS;
}
5.1 Patching requirements
Patching binaries of any kind always comes along with some requirements that must be met. The kernel file /unix is no exception, as it has to continue to run undisturbed after being patched. Being such a big and complex file requires even more attention to detail. Otherwise:
The following requirements should be met:
- ABI requirements of the kernel binary, specifically:
- Calling convention (prologue, epilogue and parameter passing)
- MIPS ISA (mips3 in this case)
- Temporary register handling
- Saved register handling
- Special register handling (at, for instance)
- Text modifications should be in-place, no growing nor shrinking of function text beyond or below existing function boundaries
- If in-place modifications are not possible, external *.o objects should be used
- Offsets to other functions should only be relative to hardcoded immutable text
- Semantics must be preserved, with exception of the patched function(s), of course
- Relocations should remain untouched (new ones may be added, though)
Sticking to these few requirements ensures the patched kernel will not break after further modifications, nor will stability be compromised.
The tools necessary to follow the patching process step by step are:
- elfdump (from software product compiler_dev, found on IRIX Development Foundation 6.5 distribution CD and IRIX Development Foundation 1.3 found in MIPSpro 7.4.4 suite distribution CDs)
- hexedit (from software product fw_hexedit, found on Freeware May 2004 CD 2 of 4)
- elfen (from patchMLOADBLK_bundle.tar)
- MIPSpro C 7.4.4 compiler suite (from MIPSpro suite distribution CDs)
The patch presented here is a conservative implementation of a patch that fixes the kernel bug described above while meeting all the requirements outlined in the previous section.
All files necessary to reproduce the patch, including the tool elfen, are provided in a patch bundle named patchMLOADBLK_bundle.tar:
Bash:
bash-2.05b# tar -vtf patchMLOADBLK_bundle.tar
rwxr-xr-x 0/0 dir Mar 10 15:30 2022 patchMLOADBLK_bundle/
rw-r--r-- 0/0 6536 Mar 10 15:29 2022 patchMLOADBLK_bundle/Makefile
rwxr-xr-x 0/0 dir Mar 10 15:30 2022 patchMLOADBLK_bundle/elfen/
rwxr-xr-x 0/0 55308 Mar 10 12:36 2022 patchMLOADBLK_bundle/elfen/elfen
rw-r--r-- 0/0 103897 Mar 10 12:36 2022 patchMLOADBLK_bundle/elfen/elfen.c
rw-r--r-- 0/0 1979 Mar 10 15:29 2022 patchMLOADBLK_bundle/patchMLOADBLK.c
rw-r--r-- 0/0 3469 Mar 10 15:29 2022 patchMLOADBLK_bundle/patchMLOADBLK.s
bash-2.05b#
Bash:
bash-2.05b# mkdir -p /root/patch
bash-2.05b# cd /root/patch
Bash:
bash-2.05b# pwd
/root/patch
bash-2.05b# tar -xf patchMLOADBLK_bundle.tar
bash-2.05b#
For instance, a backup of the system archive /var/sysgen/boot/os.a is highly recommended.
Bash:
bash-2.05b# pwd
/root/patch
bash-2.05b# cp /var/sysgen/boot/os.a os.a.orig
Bash:
bash-2.05b# pwd
/root/patch
bash-2.05b# cp /unix /unix.ok
bash-2.05b#
Bash:
bash-2.05b# pwd
/root/patch
bash-2.05b# ar -xv os.a.orig mload.o
x - mload.o
bash-2.05b# cp mload.o mload.o.orig
bash-2.05b#
An entirely in-place modification in devhold() is not feasable.
The solution would be to redirect flow from devhold() to an external function, which will be called patchMLOADBLK(). This in turn requires a new kernel object file containing the code for patchMLOADBLK(). This object file will be called patchMLOADBLK.o and will be added in the final step to /var/sysgen/boot/os.a before linking the new kernel.
To redirect control flow from devhold() to patchMLOADBLK(), a function call has to take place at some point in devhold(). To patch this in, first the file offset of devhold() must be known.
5.2.1 Get file offset of devhold()
File offsets are calculated from both the section offset (.text in this case) and symbol offset (.symtab).
Bash:
bash-2.05b# pwd
/root/patch
bash-2.05b# elfdump -h mload.o
mload.o:
**** SECTION HEADER TABLE ****
[No] Type Addr Offset Size Name
Link Info Adralgn Entsize Flags
...
[5] SHT_PROGBITS 0 0x217c 0x731c .text
0 0 0x4 0x4 0x00000006 ALLOC EXECINSTR
...
Bash:
bash-2.05b# elfdump -t mload.o
mload.o:
***** SYMBOL TABLE INFORMATION *****
[Index] Value Size Type Bind Other Shndx Name
.symtab:
...
[130] 0x35d8 140 FUNC LOCAL DEFAULT 5 devhold
...
devhold() symbol offset from beginning of mload.o file:
0x217c(8572) + 0x35d8(13784) = 0x5754(22356)
The optimal control transfer instruction would be:
Bash:
devhold:
[2579] 0x35d8: 27 bd ff e0 addiu sp,sp,-32
[2579] 0x35dc: ff bf 00 00 sd ra,0(sp)
: jal patchMLOADBLK(a0, a1)
[2579] 0x35e0: ff b7 00 08 sd s7,8(sp)
: nop
[2579] 0x35e4: 00 a0 b8 25 move s7,a1
[2579] 0x35e8: 0c 00 00 00 jal 0 { splhi }
[2579] 0x35ec: ff b0 00 10 sd s0,16(sp)
[2579] 0x35f0: 00 40 80 25 move s0,v0
: This call will be done in patchMLOADBLK(),
: but for both device types
[2581] 0x35f4: 0c 00 00 00 jal 0 { dfind_devsw }
[2581] 0x35f8: 02 e0 20 25 move a0,s7
: Return here from patchMLOADBLK(a0, a1)
[2581] 0x35fc: 10 40 00 15 beq v0,zero,0x3654
[2581] 0x3600: df b7 00 08 ld s7,8(sp)
[2586] 0x3604: dc 44 00 50 ld a0,80(v0)
[2586] 0x3608: 10 80 00 09 beq a0,zero,0x3630
[2586] 0x360c: 00 00 00 00 nop
[2586] 0x3610: 8c 01 a0 18 lw at,-24552(zero)
[2586] 0x3614: dc 21 00 68 ld at,104(at)
[2586] 0x3618: 10 24 00 05 beq at,a0,0x3630
[2586] 0x361c: 00 00 00 00 nop
[2587] 0x3620: 0c 00 00 00 jal 0 { splx }
[2587] 0x3624: 02 00 20 25 move a0,s0
[2588] 0x3628: 10 00 00 06 b 0x3644
[2588] 0x362c: 24 02 00 06 li v0,6
[2591] 0x3630: 0c 00 00 00 jal 0 { devupdate }
[2591] 0x3634: 00 40 20 25 move a0,v0
[2593] 0x3638: 0c 00 00 00 jal 0 { splx }
[2593] 0x363c: 02 00 20 25 move a0,s0
[2595] 0x3640: 00 00 10 25 move v0,zero
[2595] 0x3644: df bf 00 00 ld ra,0(sp)
[2595] 0x3648: df b0 00 10 ld s0,16(sp)
[2595] 0x364c: 03 e0 00 08 jr ra
[2595] 0x3650: 27 bd 00 20 addiu sp,sp,32
[2582] 0x3654: 0c 00 00 00 jal 0 { splx }
[2582] 0x3658: 02 00 20 25 move a0,s0
[2583] 0x365c: 10 00 ff f9 b 0x3644
[2583] 0x3660: 24 02 00 06 li v0,6
bash-2.05b#
5.2.2 Patch devhold()
With the file offset known, patching in the control transfer instructions is straightforward. Open a hex editor and go to the calculated devhold symbol offset.
The new instruction bytes can be taken from the excerpt below.
Bash:
bash-2.05b# pwd
/root/patch
bash-2.05b# hexedit mload.o
...
bash-2.05b# dis -p devhold mload.o
**** DISASSEMBLER ****
disassembly for mload.o
section .text
devhold:
[2579] 0x35d8: 27 bd ff e0 addiu sp,sp,-32
[2579] 0x35dc: ff bf 00 00 sd ra,0(sp)
: Patched instruction
[2579] 0x35e0: 0c 00 00 00 jal 0 { }
: Patched instruction
[2579] 0x35e4: 00 00 00 00 nop
[2579] 0x35e8: 0c 00 00 00 jal 0 { splhi }
[2579] 0x35ec: ff b0 00 10 sd s0,16(sp)
[2579] 0x35f0: 00 40 80 25 move s0,v0
[2581] 0x35f4: 0c 00 00 00 jal 0 { dfind_devsw }
[2581] 0x35f8: 02 e0 20 25 move a0,s7
: Return here from patchMLOADBLK(a0, a1)
[2581] 0x35fc: 10 40 00 15 beq v0,zero,0x3654
[2581] 0x3600: df b7 00 08 ld s7,8(sp)
[2586] 0x3604: dc 44 00 50 ld a0,80(v0)
[2586] 0x3608: 10 80 00 09 beq a0,zero,0x3630
[2586] 0x360c: 00 00 00 00 nop
[2586] 0x3610: 8c 01 a0 18 lw at,-24552(zero)
[2586] 0x3614: dc 21 00 68 ld at,104(at)
[2586] 0x3618: 10 24 00 05 beq at,a0,0x3630
[2586] 0x361c: 00 00 00 00 nop
[2587] 0x3620: 0c 00 00 00 jal 0 { splx }
[2587] 0x3624: 02 00 20 25 move a0,s0
[2588] 0x3628: 10 00 00 06 b 0x3644
[2588] 0x362c: 24 02 00 06 li v0,6
[2591] 0x3630: 0c 00 00 00 jal 0 { devupdate }
[2591] 0x3634: 00 40 20 25 move a0,v0
[2593] 0x3638: 0c 00 00 00 jal 0 { splx }
[2593] 0x363c: 02 00 20 25 move a0,s0
[2595] 0x3640: 00 00 10 25 move v0,zero
[2595] 0x3644: df bf 00 00 ld ra,0(sp)
[2595] 0x3648: df b0 00 10 ld s0,16(sp)
[2595] 0x364c: 03 e0 00 08 jr ra
[2595] 0x3650: 27 bd 00 20 addiu sp,sp,32
[2582] 0x3654: 0c 00 00 00 jal 0 { splx }
[2582] 0x3658: 02 00 20 25 move a0,s0
[2583] 0x365c: 10 00 ff f9 b 0x3644
[2583] 0x3660: 24 02 00 06 li v0,6
bash-2.05b#
5.2.3 Add relocation entry for patchMLOADBLK symbol
For the linker to modify the patched in jal instruction, a relocation entry is needed.
Relocation entries are organized by sections in each relocatable object. These relocation sections follow a simple naming convention.
If a section, for example .text, has objects (functions, variables, other sections, ...) that are to be relocated, the resulting .o file will have a relocation section called .rel.text or .rela.text, listing all entries that need to be relocated in section .text .
For an explanation of the differences between .rel and .rela entries, please refer to the official documentation for the MIPS ISA.
The patchMLOADBLK patch requires only .rel.text section to be modified.
To list all relocation entries in mload.o, list all sections and look for .rel.* sections:
Bash:
bash-2.05b# pwd
/root/patch
bash-2.05b# elfdump -h mload.o
mload.o:
**** SECTION HEADER TABLE ****
[No] Type Addr Offset Size Name
Link Info Adralgn Entsize Flags
[1] SHT_SYMTAB 0 0x34 0x1250 .symtab
3 0 0x4 0x10 0x00000002 ALLOC
...
[12] SHT_REL 0 0x9b84 0x20f0 .rel.text
1 0x5 0x4 0x8 0x00000000
[13] SHT_RELA 0 0xbc74 0x8e8 .rela.text
1 0x5 0x4 0xc 0x00000000
...
Bash:
bash-2.05b# pwd
/root/patch
bash-2.05b# elfdump -n .rel.text mload.o
mload.o:
**** RELOCATION INFORMATION ****
.rel.text:
Offset Symndx Type
[ 0] 0x54 mloaddebug _GPREL
[ 1] 0x60 mloaddebug _GPREL
[ 2] 0x6c mloaddebug _GPREL
[ 3] 0xa8 cap_able R_MIPS_26
[ 4] 0xc0 mloaddebug _GPREL
[ 5] 0xd0 mload R_MIPS_26
[ 6] 0xf0 munload R_MIPS_26
...
[ 1051] 0x7144 mlinfolist _GPREL
[ 1052] 0x7184 idbg_melfname R_MIPS_26
[ 1053] 0x7294 strcpy R_MIPS_26
In this case, the instructions to be modified by the linker are:
Bash:
bash-2.05b# pwd
/root/patch
bash-2.05b# dis -p devhold mload.o
**** DISASSEMBLER ****
disassembly for mload.o
section .text
devhold:
[2579] 0x35d8: 27 bd ff e0 addiu sp,sp,-32
[2579] 0x35dc: ff bf 00 00 sd ra,0(sp)
: Modify next instruction
[2579] 0x35e0: 0c 00 00 00 jal 0 { }
[2579] 0x35e4: 00 00 00 00 nop
[2579] 0x35e8: 0c 00 00 00 jal 0 { splhi }
...
bash-2.05b#
As a sidenote, if you are curious as to how a relocation entry relates to an instruction like jal, the key information is the type field of the relocation entry: R_MIPS_26.
The following relocation entry needs to be added to mload.o:
Bash:
.rel.text:
Offset Symndx Type
...
[ 1054] 0x35e0 patchMLOADBLK R_MIPS_26
Thus, an entry to the string table .strtab has to be created and added too. The relationship between all three sections is:
Relocation table [has index into] Symbol table [has index into] String table [has ASCII string]
Luckily, a tool like elfen can be used to do all of this in an automated fashion. This tool is freely available and distributed with the patch bundle patchMLOADBLK_bundle.tar.
First, the patchMLOADBLK symbol has to be added to the symbol table and string table:
Bash:
bash-2.05b# pwd
/root/patch
bash-2.05b# ln -s elfen ./elfen/elfen
bash-2.05b# ./elfen -s patchMLOADBLK 2 0 0 0 mload.o
...
Written ELF32 output file mload.o.new
bash-2.05b#
Also note that the second argument, 2, stands for the symbol type STB_GLOBAL (shows just as GLOBAL in elfdump output).
Check to see if the symbol was added correctly:
Bash:
bash-2.05b# pwd
/root/patch
bash-2.05b# elfdump -t mload.o.new
mload.o.new:
***** SYMBOL TABLE INFORMATION *****
[Index] Value Size Type Bind Other Shndx Name
.symtab:
...
[293] 0x0 0 FUNC GLOBAL DEFAULT UNDEF patchMLOADBLK
...
bash-2.05b#
Bash:
bash-2.05b# pwd
/root/patch
bash-2.05b# mv mload.o.new mload.o
bash-2.05b#
Bash:
bash-2.05b# ./elfen -r 2 293 13792 3 mload.o
...
Written ELF32 output file mload.o.new
bash-2.05b#
The second argument, 2, denotes the index of the relocation table where this entry is to be added to. Relocation table indices start with the first relocation section, not with the first section in the object file section table.
For example, a relocatable object with 10 sections in total of which 2 are relocation sections, would have the following indices:
Bash:
Section index Relocation table index
--------------------------------------------
0 .text
1 .symtab
2 .strtab
3 .rel.text 0
4 .dynsym
5 .data
6 .dynstr
7 .rel.data 1
8 .bss
9 .sbss
The third argument, 293, is the symbol table index for patchMLOADBLK (see above).
The fourth argument, 13792, is the offset in decimal to the jal instruction, starting from the beginning of the target section .text in mload.o.
Quick reminder:
Symbol offset for devhold from beginning of section: 0x35d8(13784).
Bash:
section .text
devhold:
[2579] 0x35d8: 27 bd ff e0 addiu sp,sp,-32
[2579] 0x35dc: ff bf 00 00 sd ra,0(sp)
: Modify next instruction
[2579] 0x35e0: 0c 00 00 00 jal 0 { }
[2579] 0x35e4: 00 00 00 00 nop
[2579] 0x35e8: 0c 00 00 00 jal 0 { splhi }
...
0x35d8(13784) + 2 * 4 = 13792
Last but not least, 3, directs elfen to create a relocation entry of type R_MIPS_26.
Check for the new relocation entry:
Bash:
bash-2.05b# pwd
/root/patch
bash-2.05b# elfdump -n .rel.text mload.o.new
mload.o.new:
**** RELOCATION INFORMATION ****
.rel.text:
Offset Symndx Type
...
[ 1052] 0x7144 mlinfolist _GPREL
[ 1053] 0x7184 idbg_melfname R_MIPS_26
[ 1054] 0x35e0 patchMLOADBLK R_MIPS_26
bash-2.05b#
Bash:
bash-2.05b# pwd
/root/patch
bash-2.05b# mv mload.o.new mload.o
bash-2.05b#
5.2.4 patchMLOADBLK.c
The code in patchMLOADBLK() has to check both device switch entries, bdevsw and cdevsw, and do appropriate calls to dfind_devsw():
C:
void *patchMLOADBLK(*bdevsw,
*cdevsw)
{
void *ret;
unsigned int drvidx;
// Account for patched over instructions in devhold():
// [2579] 0x35e0: ff b7 00 08 sd s7,8(sp)
// [2579] 0x35e4: 00 a0 b8 25 move s7,a1
// Adjust return address to:
// [2581] 0x35fc: 10 40 00 15 beq v0,zero,0x3654
// Account for skipped splhi() in devhold():
// [2579] 0x35e8: 0c 00 00 00 jal 0 { splhi }
// [2579] 0x35ec: ff b0 00 10 sd s0,16(sp)
// Check cdevsw
if (cdevsw != NULL)
{
// Reset t2 before calling dfind_devsw() (see point 5. below
// for details)
// t2 = 0;
ret = dfind_devsw(cdevsw);
// Check if driver descriptor was found
if (ret != NULL)
{
// Found driver descriptor
return ret;
}
// NOTE: t2
//
// At this point, the register t2 contains
// the address of the loaded modules table, drvtab.
//
// Nonetheless, this address can be NULL (see
// dfind_devsw() assembly code).
if (t2 == NULL)
{
// No drvtab address
return NULL;
}
}
// If cdevsw was not found, or was NULL, check for
// bdevsw.
// Check for bdevsw
if (bdevsw != NULL)
{
// NOTE: Block device switch
// Check if t2 is zero
if (t2 == NULL)
{
// Get pointer to loaded driver table
// in register t2 (01110b)
dfind_devsw(NULL);
// NOTE: t2
//
// At this point, register t2 contains
// the address of the loaded modules table.
//
// Beware, this address can be NULL (see
// dfind_devsw() assembly code).
if (t2 == NULL)
{
return NULL;
}
}
// Traverse the whole drvtab hash list
// t2 = drvtab;
for (drvidx = 0; drvidx < 32; drvidx++)
{
// Get next pointer to driver descriptor
desc = drvtab[drvidx];
while (desc != NULL)
{
ust = desc[12];
if (ust[188] == bdevsw)
{
return desc;
}
desc = desc[56];
}
}
}
// Couldn't find any module for the given
// device switches
return NULL;
}
5.2.5 Patching dfind_devsw()
In order to traverse the drvtab hash list from patchMLOADBLK.c, a reference to drvtab is necessary. But the drvtab symbol is local to mload.o. This poses a problem because only code inside of mload.o may access it.
The following techniques may be applied here:
- Modify any global inner function in-place (remember the requirements) so that drvtab can be accessed
- Change the drvtab symbol from LOCAL to GLOBAL by patching its symbol table entry and using yet another relocation entry to have this symbol relocated into patchMLOADBLK.c
- Extend the mload.o object with new code, which can then of course access the local drvtab hash list. The new code should be globally accessible so it can be called from patchMLOADBLK()
The most efficient technique is the first one, because it served the purpose with the fewest changes to existing code. Specifically, the following modification allows to exfiltrate the drvtab address to the caller by just invoking dfind_devsw() with any argument. This function is marked GLOBAL and thus is accessible from any other object file, including patchMLOADBLK.o.
Bash:
bash-2.05b# pwd
/root/patch
bash-2.05b# dis -p dfind_devsw mload.o
**** DISASSEMBLER ****
disassembly for mload.o
section .text
dfind_devsw:
[1193] 0x14f4: 00 04 10 c2 srl v0,a0,3
[1193] 0x14f8: 3c 01 00 00 lui at,0
[1193] 0x14fc: 30 42 00 1f andi v0,v0,31
: The first "at" will hold the drvtab address
[1193] 0x1500: 24 21 00 00 addiu at,at,0
[1193] 0x1504: 00 02 10 80 sll v0,v0,2
: At this point, "at" will be indexed and get incremented.
: The original drvtab address is lost after this instruction.
[1193] 0x1508: 00 22 08 21 addu at,at,v0
[1193] 0x150c: 8c 21 00 00 lw at,0(at)
[1193] 0x1510: 10 20 00 0b beq at,zero,0x1540
[1193] 0x1514: 00 20 10 25 move v0,at
[1195] 0x1518: 8c 47 00 0c lw a3,12(v0)
[1195] 0x151c: 8c e3 00 c0 lw v1,192(a3)
[1195] 0x1520: 10 64 00 08 beq v1,a0,0x1544
[1195] 0x1524: 00 00 00 00 nop
[1195] 0x1528: 8c e5 00 bc lw a1,188(a3)
[1195] 0x152c: 10 a4 00 05 beq a1,a0,0x1544
[1195] 0x1530: 00 00 00 00 nop
[1200] 0x1534: 8c 42 00 38 lw v0,56(v0)
[1200] 0x1538: 14 40 ff f7 bne v0,zero,0x1518
[1200] 0x153c: 00 00 00 00 nop
[1202] 0x1540: 00 00 10 25 move v0,zero
[1202] 0x1544: 03 e0 00 08 jr ra
[1202] 0x1548: 00 00 00 00 nop
bash-2.05b#
Refer to the MIPS N32/64 ABI for more details on temporary registers.
As a result, this temporary register will, in effect, exfiltrate the drvtab address.
For example, using the t2 register:
Bash:
bash-2.05b# pwd
/root/patch
bash-2.05b# dis -p dfind_devsw mload.o
**** DISASSEMBLER ****
disassembly for mload.o
section .text
dfind_devsw:
[1193] 0x14f4: 00 04 10 c2 srl v0,a0,3
[1193] 0x14f8: 3c 01 00 00 lui at,0
[1193] 0x14fc: 30 42 00 1f andi v0,v0,31
: The first "at" is now t2, and holds the drvtab address
[1193] 0x1500: 24 21 00 00 addiu t2,at,0
[1193] 0x1504: 00 02 10 80 sll v0,v0,2
: At this point t2 will be used for indexing but won't
: get incremented, instead "at" will hold the incremented value
[1193] 0x1508: 00 22 08 21 addu at,t2,v0
: Execution continues normally with drvtab's address in t2
[1193] 0x150c: 8c 21 00 00 lw at,0(at)
[1193] 0x1510: 10 20 00 0b beq at,zero,0x1540
[1193] 0x1514: 00 20 10 25 move v0,at
[1195] 0x1518: 8c 47 00 0c lw a3,12(v0)
[1195] 0x151c: 8c e3 00 c0 lw v1,192(a3)
[1195] 0x1520: 10 64 00 08 beq v1,a0,0x1544
[1195] 0x1524: 00 00 00 00 nop
[1195] 0x1528: 8c e5 00 bc lw a1,188(a3)
[1195] 0x152c: 10 a4 00 05 beq a1,a0,0x1544
[1195] 0x1530: 00 00 00 00 nop
[1200] 0x1534: 8c 42 00 38 lw v0,56(v0)
[1200] 0x1538: 14 40 ff f7 bne v0,zero,0x1518
[1200] 0x153c: 00 00 00 00 nop
[1202] 0x1540: 00 00 10 25 move v0,zero
: Since t2 was not modified up to this instruction, it still holds the drvtab address
[1202] 0x1544: 03 e0 00 08 jr ra
[1202] 0x1548: 00 00 00 00 nop
bash-2.05b#
Patching can be done by searching for the hexastring 3c0100003042001f from the second and third instruction in dfind_devsw():
dfind_devsw:
[1193] 0x14f4: 00 04 10 c2 srl v0,a0,3
[1193] 0x14f8: 3c 01 00 00 lui at,0
[1193] 0x14fc: 30 42 00 1f andi v0,v0,31
Bash:
bash-2.05b# pwd
/root/patch
bash-2.05b# hexedit mload.o
...
Ctrl-S to search for hexastring 3c0100003042001f
...
bash-2.05b#
Check for correctness:
Bash:
bash-2.05b# pwd
/root/patch
bash-2.05b# dis -p dfind_devsw mload.o
**** DISASSEMBLER ****
disassembly for mload.o
section .text
dfind_devsw:
[1193] 0x14f4: 00 04 10 c2 srl v0,a0,3
[1193] 0x14f8: 3c 01 00 00 lui at,0
[1193] 0x14fc: 30 42 00 1f andi v0,v0,31
: First patch
[1193] 0x1500: 24 2e 00 00 addiu t2,at,0
[1193] 0x1504: 00 02 10 80 sll v0,v0,2
: Second patch
[1193] 0x1508: 01 c2 08 21 addu at,t2,v0
[1193] 0x150c: 8c 21 00 00 lw at,0(at)
[1193] 0x1510: 10 20 00 0b beq at,zero,0x1540
[1193] 0x1514: 00 20 10 25 move v0,at
[1195] 0x1518: 8c 47 00 0c lw a3,12(v0)
[1195] 0x151c: 8c e3 00 c0 lw v1,192(a3)
[1195] 0x1520: 10 64 00 08 beq v1,a0,0x1544
[1195] 0x1524: 00 00 00 00 nop
[1195] 0x1528: 8c e5 00 bc lw a1,188(a3)
[1195] 0x152c: 10 a4 00 05 beq a1,a0,0x1544
[1195] 0x1530: 00 00 00 00 nop
[1200] 0x1534: 8c 42 00 38 lw v0,56(v0)
[1200] 0x1538: 14 40 ff f7 bne v0,zero,0x1518
[1200] 0x153c: 00 00 00 00 nop
[1202] 0x1540: 00 00 10 25 move v0,zero
[1202] 0x1544: 03 e0 00 08 jr ra
[1202] 0x1548: 00 00 00 00 nop
bash-2.05b#
5.2.6 From patchMLOADBLK.c to patchMLOADBLK.s
Since access to registers is needed, as well as to the stack (refer to 5.2.4 patchMLOADBLK.c for details), assembly language would be more appropriate than C.
A possible implementation could be:
C:
#include <regdef.h>
.file 1 "/root/patch/patchMLOADBLK_bundle/patchMLOADBLK.c"
.set noreorder
.set noat
# /usr/lib32/cmplrs/be::7.4
#-----------------------------------------------------------
# Compiling patchMLOADBLK.c (/tmp/ctmB.BAAa002H4)
#-----------------------------------------------------------
#-----------------------------------------------------------
# Options:
#-----------------------------------------------------------
# Target:R5000, ISA:mips4, Pointer Size:32
# -O0 (Optimization level)
# -g2 (Debug level)
# -m2 (Report advisories)
#-----------------------------------------------------------
.section .text, 1, 0x00000006, 4, 4
.text:
.section .text
# Program Unit: patchMLOADBLK
.ent patchMLOADBLK
.globl patchMLOADBLK
patchMLOADBLK: # 0x0
.dynsym patchMLOADBLK sto_default
.frame $sp, 32, $31
.mask 0x80000000, -24
# a = 0
# lcl_spill_temp_0 = 8
.loc 1 7 1
# 3 extern int splhi(void);
# 4
# 5
# 6 int patchMLOADBLK(int arg)
# 7 {
.BB1.patchMLOADBLK: # 0x0
daddiu sp,sp,-32 #
sd s2,0(sp) # dfind_devsw
sd s1,16(sp) # Save s1
or s1,a0,zero # Move s1,a0
sd s7,40(sp) # Adjusted
or s7,a1,zero # Move s7,a1
# Get dfind_devsw address relative to ra (ra = ra + X=-2109)
daddi s2,ra,-8436
# Get final return address in devhold() (ra = ra + 20)
daddiu ra,ra,20
# Store return address in own stack frame
sd ra,8(sp) # Store return address
.type splhi, stt_func
lui AT,%hi(splhi) # splhi
daddiu AT,AT,%lo(splhi) # splhi
jalr AT # splhi
nop
sd s0,48(sp) # Adjusted
or s0,v0,zero # Move s0,v0
.BB2.patchMLOADBLK: # 0x54
# Reset return value
or v0,zero,zero # Move v0,zero
# Reset t2 value
or t2,zero,zero
# Check for char devsw
beq s7,zero,BLK_DEVSW # If no char devsw, try block devsw
nop
# jalr dfind_devsw (char devsw)
jalr s2
or a0,s7,zero # Move a0,s7
bne v0,zero,FOUND # If dfind_devsw() != 0
nop
# Check for t2 == NULL
beq t2,zero,NOT_FOUND
nop
BLK_DEVSW:
# Check for block devsw
beq s1,zero,NOT_FOUND
or a0,s1,zero
# Check for drvtab from char devsw
bne t2,zero,LOOP_START
nop
# Call dfind_devsw() to get pointer to drvtab
jalr s2
nop
# Check for t2 == NULL
beq t2,zero,NOT_FOUND
nop
### Loop through drvtab
LOOP_START:
# Loop counter
ori t0,zero,0
# Loop end condition value
ori t1,zero,32
LOOP_SLOTS:
# Get next pointer in drvtab
lw v0,0(t2)
beq v0,zero,NEXT_SLOT
nop
LOOP_CHAIN:
# Get m_data
lw a3,12(v0)
lw a1,188(a3)
# If a0 == bdevsw_field: goto FOUND
beq a1,a0,FOUND
nop
# Get m_next field
lw v0,56(v0)
# If m_next != NULL
bne v0,zero,LOOP_CHAIN
nop
NEXT_SLOT:
addiu t2,t2,4
addiu t0,t0,1
# Check for end of loop
beq t0,t1,NOT_FOUND
nop
b LOOP_SLOTS
nop
NOT_FOUND:
# Set return value
or v0,zero,zero # Move v0,zero
.loc 1 29 2
# 27
# 28
# 29 return arg;
FOUND:
ld s2,0(sp) # Restore s2
ld ra,8(sp) # lcl_spill_temp_0
ld s1,16(sp) # Restore s1
daddiu sp,sp,32 #
jr ra # Return to devhold()
nop #
.end patchMLOADBLK
.section .text
.align 2
.gpvalue 30720
5.2.7 From patchMLOADBLK.s to patchMLOADBLK.o
A Makefile is provided to compile the patchMLOADBLK.s file. Basically, it consists of the same compiler flags used to compile loadable kernel modules.
Bash:
bash-2.05b# pwd
/root/patch
bash-2.05b# cd patchMLOADBLK
bash-2.05b# smake patchMLOADBLK.o
...
bash-2.05b#
5.2.8 Creating a new kernel
The final step consists of replacing the original object mload.o file with the patched one and adding the new external patchMLOADBLK.o object for the linker to find the global patchMLOADBLK function symbol.
The following procedure can be used:
Bash:
bash-2.05b# pwd
/root/patch
bash-2.05b# ar -rv /var/sysgen/os.a mload.o patchMLOADBLK/patchMLOADBLK.o
r - mload.o
r - patchMLOADBLK.o
bash-2.05b#
Bash:
bash-2.05b# autoconfig
Automatically reconfiguring the operating system.
Reboot to start using the reconfigured kernel.
bash-2.05b#
5.2.9 patchMLOADBLK.sh
A patch script, patchMLOADBLK.sh, is provided as part of patchMLOADBLK.tar to automate the patching process. Invoke the script with -h to get the help menu.
Last edited: