IRIX KERNEL PATCH (patchMLOADBLK)

TruHobbyist

Member
Jul 27, 2019
41
38
18
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:

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#
When trying to mount a filesystem, the following error is shown sporadically:

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#
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:
  • 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
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:
  1. User space mount(1M) returned ENXIO
  2. Filesystem type used with mount was EFS
  3. mount is a user program, a library function and also a syscall
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:

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#
The crucial part is:

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
...
The kernel debugger shows the dfind_devsw() function is invoked at this point:

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
...
In C code, this would translate to:

C:
devhold(a0, a1)
{

    ...

    ret = dfind_devsw(a0=s7=a1);
    if (ret == 0)
    {
        return ENXIO;
    }

    ...

}
The decompiled devhold() function is semantically equivalent to:

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;
}
Dynamic debugging yields the following:
  • 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)
It follows an exclusive OR logic pattern.

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#
The decompiled dfind_devsw() function is semantically equivalent to:

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;
}
Dynamic debugging yields the following:
  • 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.
4.4 The DLKM system logic

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
The index is calculated using a simple hash algorithm as can be seen in dfind_devsw():

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:
  1. Loading of a CHR only driver with subsequent access of a driver owned CHR device file



  2. Loading of a CHR and BLK driver with subsequent access of a driver owned CHR device file



  3. Loading of a BLK only driver with subsequent mount of a driver owned BLK device file



  4. 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;
}
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:
  • 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)
5.2 patchMLOADBLK

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#
First, create a working directory:

Bash:
bash-2.05b# mkdir -p /root/patch
bash-2.05b# cd /root/patch
Unpack the patch bundle patchMLOADBLK_bundle.tar:

Bash:
bash-2.05b# pwd
/root/patch
bash-2.05b# tar -xf patchMLOADBLK_bundle.tar
bash-2.05b#
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.

Bash:
bash-2.05b# pwd
/root/patch
bash-2.05b# cp /var/sysgen/boot/os.a os.a.orig
A copy of the originally running kernel /unix should be copied under / with a descriptive short name:

Bash:
bash-2.05b# pwd
/root/patch
bash-2.05b# cp /unix /unix.ok
bash-2.05b#
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).

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#
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).

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

...
Section offset from beginning of mload.o file: 0x217c.

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

...
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:

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

...
A listing of relocation entries from .rel.text:

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
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:

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#
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:

Bash:
.rel.text:
        Offset    Symndx        Type

...

[ 1054] 0x35e0    patchMLOADBLK  R_MIPS_26
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:

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#
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:

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#
Rename mload.o.new to mload.o:

Bash:
bash-2.05b# pwd
/root/patch
bash-2.05b# mv mload.o.new mload.o
bash-2.05b#
Second, add the relocation entry for devhold() to call patchMLOADBLK():

Bash:
bash-2.05b# ./elfen -r 2 293 13792 3 mload.o

...

Written ELF32 output file mload.o.new
bash-2.05b#
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:

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
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).

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 }

...
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:

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#
And don't forget to rename the output file from mload.o.new to mload.o:

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:
  1. Modify any global inner function in-place (remember the requirements) so that drvtab can be accessed
  2. 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
  3. 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#
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:

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#
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

Bash:
bash-2.05b# pwd
/root/patch
bash-2.05b# hexedit mload.o

...

Ctrl-S to search for hexastring 3c0100003042001f

...

bash-2.05b#
The new instruction bytes can be taken from the excerpt below.

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
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.

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#
Finally, relink a new kernel by invoking autoconfig:

Bash:
bash-2.05b# autoconfig
Automatically reconfiguring the operating system.

Reboot to start using the reconfigured kernel.
bash-2.05b#
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.
 
Last edited:

drmadison

Member
Jun 30, 2020
33
20
8
Seriously, what a fantastic writeup.
I've just recently started diving into documentation on creating new drivers and this is one of the first posts I've seen people actually doing it and diving into more technical details of the kernel.
 

Elf

Storybook / Retired, ex-staff
Feb 4, 2019
792
252
63
Mountain West (US)
Very nice writeup, and the patching solution, calling out to your own code and taking advantage of autoconfig to rebuild the kernel was nice and clean. I admire the amount of effort invested. I would guess this is not your first time with binary analysis and patching ELF files? :)

A few somewhat simple questions:
  • Were any different techniques necessary to debug across the userland / kernel space barrier? I have not done any of this under IRIX. From what I remember in Linux one needs to use kgdb or similar?
  • Do you use a tool to automatically generate diagrams from stack traces like that, or do you construct the diagrams manually?
 

TruHobbyist

Member
Jul 27, 2019
41
38
18
say "Hi!";

A few somewhat simple questions:
  • Were any different techniques necessary to debug across the userland / kernel space barrier? I have not done any of this under IRIX. From what I remember in Linux one needs to use kgdb or similar?
Use synchronous printf from kernel, sync_printf() (undocumented). User/kernel transitions must be dealt with by synchronizing both (user daemon/command and kernel module) with yet a third kernel module. That's the technique I came up with.

  • Do you use a tool to automatically generate diagrams from stack traces like that, or do you construct the diagrams manually?
Manually.
 

Geoman

Member
Dec 28, 2019
53
22
8
Germany
😮 wow ! to say that I'm impressed would be an understatement ! It's an honour to have such capacities among us in the community!
 

stormy

Active member
Jun 23, 2019
150
63
28
@TruHobbyist

My Octane systems are updated to the most recent kernel patches, when trying to install your patch I get:

Code:
OCTANEM 4# ./patchMLOADBLK.sh -i
INFO: Installing patch MLOADBLK on IRIX 6.5.30 (64 bits)
INFO: System cannot be patched, checksums differ for system mload.o and original mload.o. Please report back this error. Thank you!
INFO: Restoring files
INFO: Files have been restored
I think I have all the patches inside here applied:
 
  • Like
Reactions: TruHobbyist

TruHobbyist

Member
Jul 27, 2019
41
38
18
Code:
OCTANEM 4# ./patchMLOADBLK.sh -i
INFO: Installing patch MLOADBLK on IRIX 6.5.30 (64 bits)
INFO: System cannot be patched, checksums differ for system mload.o and original mload.o. Please report back this error. Thank you!
INFO: Restoring files
INFO: Files have been restored
Hey stormy, the patch provided is for a vanilla IRIX install because that's what most people I know use. When other patches are installed, they may modify the mload.o kernel object and hence the patchMLOADBLK installer refuses to proceed.
 

stormy

Active member
Jun 23, 2019
150
63
28
That's a shame! You'd think the most logical start point would be from the last kernel patch officially from SGI. This also means anyone who installs your kernel patch potentially can't install any of the SGI updates? Sorry for criticism I know you've worked hard, but my logic is anyone with the skills to install IRIX on an ancient workstation also possesses the skills to update to the latest patch before applying yours.
 

TruHobbyist

Member
Jul 27, 2019
41
38
18
That's a shame! You'd think the most logical start point would be from the last kernel patch officially from SGI.
It's a valid POV. My POV was straight pragmatic: what are most people using actually? Any other assumption would require intimate knowledge about what patches people installed on their systems.

This also means anyone who installs your kernel patch potentially can't install any of the SGI updates?
I'm not familiar with how SGI patches work. I would need to go through every single patch to be able to give you an authoritative answer.

Sorry for criticism I know you've worked hard, but my logic is anyone with the skills to install IRIX on an ancient workstation also possesses the skills to update to the latest patch before applying yours.
No, please. Criticism has the power of making things better because you can adapt to other's wishes and necessities. In this case, sadly, I cannot. But on the upside this bug won't affect your system other than in extremely unlikely corner cases.

Btw, hats off for patching your systems, stormy.


To be very clear about the use case of this patch: it was conceived as a patch for a default IRIX installation for people that either wish to get rid of this bug or use File Looper without random mounting errors. No other use cases were considered.
 

About us

  • Silicon Graphics User Group (SGUG) is a community for users, developers, and admirers of Silicon Graphics (SGI) products. We aim to be a friendly hobbyist community for discussing all aspects of SGIs, including use, software development, the IRIX Operating System, and troubleshooting, as well as facilitating hardware exchange.

User Menu