rod*_*itu 0 x86 assembly bit-manipulation masm bit-fields
我有一个问题需要解决,但我不知道如何解决。我正在询问如何解决这个问题的一般想法。我有一个内存地址,在 ESI 中。内存代表某种简化的 ASCII 编码,其中 5 位依次表示一个特定字符。内存以五位结尾 - 00000b。为了转换为正常的 8 位 ASCII,必须将 60H 添加到每个 5 位值中。我想将每个 5 位编码字符存储为地址 EDI 下的 8 位 ASCII 编码。EDI 也必须以 0 - 00000000b 结尾。
示例: [ESI] = 00011 00010 11010 00000b [EDI] = 01100011 01100010 01111010 00000000b
我将如何从 esi 中逐个提取每 5 位?
首先,您需要确定 5 位代码在输入字节流中的排列方式。
\n我们需要从整个输入数据字节开始:如果我们说 [ESI] = 00011 00010 11010 00000,我们就跳过了 8 位字节解释和解码中重要的第一步。
\n就像字节顺序一样,有两种明智的方法可以完成此操作。\xc2\xa0 其中一种更适合 intel 指令集。
\n 76543210 bit numbering position\n+--------+\n| | first input data byte\n+--------+\n ..... (A) possible position of 5-bit code within first input byte\n ..... (B) alternate position of 5-bit code within first input byte\nRun Code Online (Sandbox Code Playgroud)\n选择(A)表示第一个5位代码位于第一个输入数据字节的最低有效位,而选择(B)表示第一个5位代码位于第一个输入数据字节的最高有效位字节。
\n由于英特尔机器是小端字节序并且从 LSB 到 MSB 对位进行编号,因此选择 (A) 在该处理器上更为自然。
\n选择 (A) 将按如下方式查看输入字节流:
\n 1 2 3 input byte stream byte number\n76543210 76543210 76543210 input byte bit position numbers\nhgfedcba ponmlkji xwvutsrq input byte stream, individual bits as letters\n22211111 43333322 55554444 5-bit code number for each bit\n / / \\ \\\n / / | |\n / / | |\n/ / | |\nedcba jihgf onmlk tsrqp yxwvu 5-bit code output stream\n 1 2 3 4 5 5-bit code output number\nRun Code Online (Sandbox Code Playgroud)\n您可以看到 5 位代码 #2 的位跨越了字节边界,就像 5 位代码 #4 一样。
\n选择 (B) 时,视图将如下所示:
\n 1 2 3 input byte stream byte number\n76543210 76543210 76543210 input byte bit position numbers\nabcdefgh ijklmnop qrstuvwx input byte stream, individual bits as letters\n11111222 22333334 44445555 5-bit code number for each bit\n| | \\ |\\ \\\\ \\\n| | | | \\ \\\\ |\n| | | | | | \\ |\nabcde fghij klmno pqrst uvwxy 5-bit code output stream\n 1 2 3 4 5 5-bit code output number\nRun Code Online (Sandbox Code Playgroud)\n这更像是您在文本中显示的内容,因此您确实需要弄清楚要使用哪个选择。\xc2\xa0 具体示例将显示第一个 5 位代码位于第一个数据字节中的位置。
\n无论哪种方式,一种方法是接收 8 位字节并输出 5 位代码!
\n处理大小不匹配的方法是使用一个位队列(可变长度)作为缓冲区,当位队列中的位数小于5时,有条件地将整个输入字节放入位队列中。
\n(这是一种缓冲方法;缓冲无处不在,例如当应用程序想要流入或流出字符/字节(想想printf文本行)而设备(如文件系统)想要写入块时,可以满足大小不匹配的情况(4096 块)。)
因此,bit_queue 的大小为 4(不足以生成 5 位代码的最大值)+ 8(一个全新的字节)= 队列中最大 \xe2\x80\x94 的 12 位,因此,bit_queue将适合 16 位寄存器,而且我们还需要另一个变量作为位数(最大值 12)。
\n基本上,我们可以有一个循环,对于每次迭代,始终输出一个 5 位代码,并有条件地消耗一个 8 位输入数据字节(例如,在需要时)。\xc2\xa0 这种方法将为队列提供足够的数据。位,每次迭代输出 5 位代码。
\n更正式地说:
\n int bit_count = 0;\n uint_16 bit_queue = 0;\n\nloop:\n if bit_count < 5 then // need to add more bits? so get a byte\n temp8 = *sourcePointer++ // fetch byte/8-bits from memory\n temp16 = temp8 // zero extend temp8 to 16 bits\n // to match the size of bit_queue\n bit_queue = merge(bit_queue, temp16)\n bit_count += 8 // we just added 8 bits to the bit queue\n endif\n temp5 = extract5(bit_queue) // take the 5 bits of interest\n bit_queue = remove5(bit_queue) // discard 5 bit code from the bit queue\n bit_count -= 5 // we just removed 5 bits from bit queue\n *destinationPointer++ = temp5 + 0x60\n if temp5 != 0 goto loop\nRun Code Online (Sandbox Code Playgroud)\n我在伪代码中将 merge、extract5 和 remove5 编写为函数,但它们是简单的Shift、Or和Mask逻辑操作,要内联完成(而不是调用函数)。
\n将它们更抽象地显示为函数的原因是它们的确切定义取决于选择 (A) 与 (B)。
\n对于选择 (A),来自每个下一个输入数据字节的新 8 位位于队列的高(更有效)侧,并且从位队列的 LSB 部分中删除 5 位代码,而对于选择 ( B) 来自每个下一个输入数据字节的相反的 \xe2\x80\x94 新 8 位位于队列的低/LSB 侧,并且从位队列的 MSB 部分中删除 5 位代码。
\n使用选项 (A) 的示例:
\n最初,bit_queue 为空,bit_count 为零。\xc2\xa0 因此,将输入数据字节输入到 bit_queue 的条件操作只是将第一个数据字节加载到 bit_queue(合并操作与空队列合并,因此将零位置的简并移位,并简单地按原样复制该值。)
\n 00000000hgfedcba bit_queue loaded with first byte\n\n 8 bit_count\nRun Code Online (Sandbox Code Playgroud)\n函数extract5 .\xc2\xa0 5 位代码只是带有 0x1f 的 bit_queue 的掩码,产生
\n 00000000hgfedcba bit_queue loaded with first byte\n 0000000000011111 mask, 0x1f \n ----------------- & mask operation\n 00000000000edcba 5-bit code result\nRun Code Online (Sandbox Code Playgroud)\n我们可以添加 0x60 并将其发送到输出数据流。
\n下一个函数remove5:
\n bit_queue >>= 5 discard 5 bits by shifting them off the end\n\n 00000000hgfedcba bit_queue: before shift\n 0000000000000hgf bit_queue: after shift\nRun Code Online (Sandbox Code Playgroud)\n请注意,为了删除,我们还将 bit_count 减 5,表示 bit_queue 中只剩下 3 位。
\n最后merge,它将 bit_queue 和 temp16 作为输入(这是第二次迭代):
\n 0000000000000hgf bit_queue: remaining bits after shift\n 00000000ponmlkji temp16: next input data byte, zero extended to 16 bits\nRun Code Online (Sandbox Code Playgroud)\n我们需要移动 temp16,以便 temp16 中的“i”与 bit_queue 中“h”旁边的“0”对齐。\xc2\xa0 移位量就是 bit_count:即 bit_queue 中的位数我们需要保留,因此通过将 temp16 移位该量,这将在输入字节的数据后面插入 bit_count 零。
\n temp16 <<= bit_count // bit_count is 3\n\n 00000000ponmlkji temp16: before shift\n 00000ponmlkji000 temp16: after shift\nRun Code Online (Sandbox Code Playgroud)\n最后一个“或”操作将移位后的 temp16 合并到 bit_queue 中:
\n 0000000000000hgf bit_queue after removing the first 5 bits\n 00000ponmlkji000 temp16: next input data byte, shifted\n ----------------- | or operation\n 00000ponmlkjihgf new bit_queue after merge operation, \n holding 3 bits from input data byte 1,\n and 8 bits from input data byte 2\nRun Code Online (Sandbox Code Playgroud)\n请注意,bit_count 现在变为 3+8=11。
\n此 bit_queue 现在已准备好提供另一个 5 位代码,依此类推。
\n这种方法的优点之一是它不需要处理器的未对齐加载功能,因此适用于 MIPS 以及 x86。\xc2\xa0 它还仅加载每个输入数据字节一次。
\n作为完全替代的方法,我们可以从其索引/位置号及其占用的两个相邻字节中提取任何给定的 5 位值。 \xc2\xa0 这样,我们只需提取每个 5 位值。
\n提取 5 位值首先通过计算 5 位值 \xe2\x80\x94 的第一位的位位置偏移量来完成,该偏移量可用于计算(最多)两个字节的字节偏移量5 位字段,以及提取它的移位量。
\n位位置偏移量是通过将 5 位索引乘以 5 来计算的(或者如果在线性循环中完成,则每次迭代将 5 添加到位位置计数器)。\xc2\xa0 通过除以该位位置偏移量来计算字节偏移量除以 8(整数除法层,这里除以 8 是 3 的简单右移)。\xc2\xa0 最后添加到输入数组的基数中,我们得到包含 5 位的第一位的字节地址\xc2\xa0 由于 5 位字段在第一个字节中至少有一个位,因此它要么完全包含在第一个字节中,要么包含在第一个字节和下一个连续字节的组合中,因此我们可以在该地址加载一个 16 位值,并获得完整的 5 位字段。
\n5 位字段的扫描过程如下:
\n bit_offset = 0\nloop:\n byte_offset = bit_offset >> 3\n address = sourcePointer + byte_offset // regular/unscaled addition\n temp16 = *address // this is a 16-bit load ***\n temp16 = temp16 >> bit_offset\n temp8 = temp16 & 0x1f\n *destinationPointer++ = temp8 + 0x60\n bit_offset += 5\n if temp8 != 0 goto loop\nRun Code Online (Sandbox Code Playgroud)\n***此方法在 5 位索引处加载两个字节(无论需要与否),同时将下一次迭代的字节位置最多增加 1 个字节。\xc2\xa0 它会多次重新加载输入流的每个字节。\xc2\ xa0 它将潜在感兴趣的两个字节加载到 16 位数据中,然后转移以提取感兴趣的 5 位字段。\xc2\xa0 天真的它依赖于未对齐的访问,尽管它可以轻松地适用于不对齐的访问通过相当简单的两字节加载和重组序列支持未对齐访问。