一、工欲善其事必先利其器
我在系统上通过如下2条命令创建了一个ext4格式的“内存磁盘”
mkfs.ext4 /dev/ram0
mount /dev/ram0 /ram
接着拷贝一些文件到 /ram目录下,其中一个就是 /ram/uio/uio_begin.sh
首先通过 dumpe2fs /dev/ram0了解到文件系统的如下信息
Blocks per group: 32768
Inodes per group: 8192
Inode blocks per group: 512
First block: 0
Block size: 4096
Inode size: 256
sh-4.3# stat /ram/uio/uio_begin.sh
File: /ram/uio/uio_begin.sh
Size: 168 Blocks: 8 IO Block: 4096 regular file
Device: 100h/256d Inode: 49155 Links: 1
Access: (0750/-rwxr-x---) Uid: ( 0/ root) Gid: ( 0/ root)
Access: 2019-02-01 00:22:34.000000000
Modify: 2019-02-01 00:22:34.000000000
Change: 2019-02-01 00:22:34.000000000
二、定位文件inode所在的block
从上面的信息我们了解到文件/ram/uio/uio_begin.sh的inode号为49155。
而一个block组包含8192个inodes,因而uio_begin.sh所在的group这样计算: 49155/8192 = 6 ... 3,所以uio_begin.sh在第7个block组,即group6(从group0开始)。
了解到uio_begin.sh文件所在的组为group6,然后通过group6中inode table起始地址 + uio_begin.sh的inode偏移就可计算出uio_begin.sh文件inode位置。
(1)寻找偏移
uio_begin.sh所在的inodetbl偏移:49155%8192 -1 = 2(inodes)
一个inode大小为256B, so文件在inode table中的偏移为2*256B=512=0x200
(2)找到group6中inode table的block,此处可以借助工具dumpe2fs /dev/ram0获取
Group 6: (Blocks 196608-229375)
Block bitmap at 196608 (+0)
Inode bitmap at 196609 (+1)
Inode table at 196610-197121 (+2)
32251 free blocks, 8189 free inodes, 1 directories
Free blocks: 197122-198655, 198659-229375
Free inodes: 49156-57344
从上面的信息可以看到group6中Inode table起始地址在block 196610,so,下面我们就dump出此inode table的内容瞧一瞧:
dd if=/dev/ram0 bs=4096 skip=196610 | hexdump -C -n 2048
sh-4.3# dd if=/dev/ram0 bs=4096 skip=196610 | hexdump -C -n 2048
00000000 e8 41 00 00 00 10 00 00 5a 91 53 5c 4a 91 53 5c |.A......Z.S\J.S\|
00000010 4a 91 53 5c 00 00 00 00 00 00 02 00 08 00 00 00 |J.S\............|
00000020 00 00 00 00 00 00 00 00 00 08 03 00 00 00 00 00 |................|
00000030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
00000060 00 00 00 00 ac 81 4d 5b 00 00 00 00 00 00 00 00 |......M[........|
00000070 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
00000100 e8 81 00 00 c8 00 00 00 4a 91 53 5c 4a 91 53 5c |........J.S\J.S\|
00000110 4a 91 53 5c 00 00 00 00 00 00 01 00 08 00 00 00 |J.S\............|
00000120 00 00 00 00 00 00 00 00 01 08 03 00 00 00 00 00 |................|
00000130 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
00000160 00 00 00 00 ad 81 4d 5b 00 00 00 00 00 00 00 00 |......M[........|
00000170 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
00000200 e8 81 00 00 a8 00 00 00 4a 91 53 5c 4a 91 53 5c |........J.S\J.S\|
00000210 4a 91 53 5c 00 00 00 00 00 00 01 00 08 00 00 00 |J.S\............|
00000220 00 00 00 00 00 00 00 00 02 08 03 00 00 00 00 00 |................|
00000230 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
00000260 00 00 00 00 ae 81 4d 5b 00 00 00 00 00 00 00 00 |......M[........|
00000270 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
文件inode的偏移为0x200,我们看看inode中第一个字段是否符预期:
inode的第一个成员字段为:
0x0 __le16 i_mode
它占16个bit,其值为:0x41e8,即8进制的100750,
即100000(表示regular file) | 750(访问权限),这与stat stat /ram/uio/uio_begin.sh结果一致:
sh-4.3# stat /ram/uio/uio_begin.sh
File: /ram/uio/uio_begin.sh
Size: 168 Blocks: 8 IO Block: 4096 regular file
Device: 100h/256d Inode: 49155 Links: 1
Access: (0750/-rwxr-x---) Uid: ( 0/ root) Gid: ( 0/ root)
Access: 2019-02-01 00:22:34.000000000
Modify: 2019-02-01 00:22:34.000000000
Change: 2019-02-01 00:22:34.000000000
三、从inode到文件数据块
在文件较小的时候使用直接data block存储。这里就只考虑这种情况。
inode主要包含两部分内容:文件属性和数据块指针(60Bytes),其中数据块指针放在inode->i_block[15]数组中:
0x28 60 bytes i_block[EXT4_N_BLOCKS=15]
通过偏移inode结构+偏移0x28,然后hexdump找到:
00000220 00 00 00 00 00 00 00 00 02 08 03 00 00 00 00 00 |................|
第一个数据库块是0x00030802,即十进制229376
dd if=/dev/ram0 bs=4096 skip=198658 | hexdump -C -n 2048
sh-4.3# dd if=/dev/ram0 bs=4096 skip=198658 | hexdump -C -n 2048
00000000 20 65 63 68 6f 20 30 30 30 30 3a 30 30 3a 30 33 | echo 0000:00:03|
00000010 2e 30 20 3e 20 2f 73 79 73 2f 62 75 73 2f 70 63 |.0 > /sys/bus/pc|
00000020 69 2f 64 72 69 76 65 72 73 2f 65 31 30 30 30 2f |i/drivers/e1000/|
00000030 75 6e 62 69 6e 64 20 0a 20 65 63 68 6f 20 22 38 |unbind . echo "8|
00000040 30 38 36 20 30 78 31 30 30 65 22 20 3e 20 2f 73 |086 0x100e" > /s|
00000050 79 73 2f 62 75 73 2f 70 63 69 2f 64 72 69 76 65 |ys/bus/pci/drive|
00000060 72 73 2f 75 69 6f 5f 70 63 69 5f 67 65 6e 65 72 |rs/uio_pci_gener|
00000070 69 63 2f 6e 65 77 5f 69 64 20 0a 20 6c 73 20 2d |ic/new_id . ls -|
00000080 6c 20 2f 73 79 73 2f 62 75 73 2f 70 63 69 2f 64 |l /sys/bus/pci/d|
00000090 72 69 76 65 72 73 2f 75 69 6f 5f 70 63 69 5f 67 |rivers/uio_pci_g|
000000a0 65 6e 65 72 69 63 2f 0a 00 00 00 00 00 00 00 00 |eneric/.........|
000000b0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
然后我们验证一下里面的内容是否一致:
sh-4.3# cat /ram/uio/uio_begin.sh
echo 0000:00:03.0 > /sys/bus/pci/drivers/e1000/unbind
echo "8086 0x100e" > /sys/bus/pci/drivers/uio_pci_generic/new_id
ls -l /sys/bus/pci/drivers/uio_pci_generic/
果然一致,牛逼。
四、从文件名定位文件
如果我们只知道"/ram/uio/uio_begin.sh"这个字符串,在不借助"stat" 工具的情况下,如何找到uio_begin.sh的inode呢?
要回答上面的问题,我们首要讲几个事实。
首先,系统中根目录文件"/"的inode号固定为2;
其次,与普通文件类似,目录文件也有一个inode和至少一个用于存储数据的block,inode 记录该目录的相关权限属性以及数据block指针。
不同的是,目录文件的数据block中存储的是记录在这个目录下的文件名与该文件名占用的inode号,具体如下:
struct ext2_dir_entry_2 {
__le32 inode; /* Inode number */
__le16 rec_len; /* Directory entry length */
__u8 name_len; /* Name length */
__u8 file_type;
char name[]; /* File name, up to EXT2_NAME_LEN */
};
我们来分解一下"/ram/uio/uio_begin.sh",它包含 根目录、目录ram、目录uio和普通文件uio_begin.sh总共4个文件。
有了上面的知识,我们就可以先通过根目录"/"的inode号找到"/"的数据块;这个数据块中包含着目录文件"ram"的inode号和名字;
有了"ram"的inode,就相当于有了"ram"数据---目录项,即可找到"uio"这个目录文件;
同理,又可以通过"uio"数据块中存储的目录项最终定位到uio_begin.sh文件的inode号。
找到uio_begin.sh文件的inode也就意味着我们只需重复上面的第二、第三节中的步骤即可。