在汇编中为正则表达式 '[ab][^r]+r]' 实现匹配器

Implementing a matcher for the regex '[ab][^r]+r]' in assembly

我的汇编代码需要帮助。我需要使用编写代码,它会找到适合我的正则表达式的范围。

我的正则表达式:[ab][^r]+r,所以首先我要查找是否有 'a' 或 'b',然后跳转到 'Start' 部分。现在我有一个问题如何只保存这封信的第一次出现。 程序应显示:5, 10 - 这意味着匹配字符串从第 5 个位置开始,长度为 10。我想保存在 'ecx''edx' 注册表中的程序结果(或者我可以简化它吗?)

我将不胜感激所有建议和帮助:)

这是一个代码:

#include <stdio.h>

int main(void) 
{
  char *s = "fqr  b qabxx  xryc pqr"; // string length= 22, first occurence: 5 position, second one: 9
  int x, y;

asm volatile (
".intel_syntax noprefix;"
"mov eax, %2;"

"xor ecx, ecx;"
"xor edx, edx;"

"lea ebx, [eax];" 

"loop:"
  "mov al, [ebx];" 
  "or al, al;" 
  "jz Finish;"
  "inc edx;"

  "cmp al, 'a';" 
  "je Start;"

  "cmp al, 'b';"
  "je Start;"

  "jmp JumpNext;"

 "Start:" 
   "mov ecx, edx;"
   "jmp loop;"

  "JumpNext:"
    "inc ebx;"
    "jmp loop;"

 "Finish:"
  "mov %0, ecx;"
  "mov %1, edx;"

".att_syntax prefix;"
:"=r" (x), "=r" (y)
:"r" (s)
:"eax", "ebx", "ecx", "edx"
);

printf("%d, %d \n", x, y);
return 0; 
}

编辑:这是完成的代码:

 #include <stdio.h>


int main(void)
{
  char *s = "fqr  b qabxx xryc pqr"; 
  int x, y;

  asm volatile (
    ".intel_syntax noprefix;"

    "xor ecx, ecx;" // First occurrence of letter 'a' or 'b'
    "mov edx, 1;" // Matching string length

    "lea ebx, [%2];"

    "loop:"
      "mov al, [ebx];"
      "cmp al, 0;"
      "jz ThereisNoChars;" 

      "cmp al, 'a';" 
      "je FoundAorB;"

      "cmp al, 'b';" 
      "je FoundAorB;"

      "inc ecx;"
      "inc ebx;"
      "jmp loop;"

    "FoundAorB:"
      "inc ebx;"
      "inc edx;" 

      "mov al, [ebx];"
      "or al, al;"
      "jz ThereisNoChars;"

      "cmp al, 'r';"
      "je isRafterAorB;"
      "jne ThereIsNoR;"

    "isRafterAorB:"
      "mov edx, 1;"
      "inc ebx;" 
      "inc ecx;"
      "jmp loop;"

   "ThereIsNoR:"
      "inc ebx;"
      "inc edx;"
      "mov al,[ebx];"
      "or al, al;"
      "jz ThereisNoChars;"
      "cmp al, 'r';"
      "je Finish;"
      "jmp ThereIsNoR;"

   "ThereisNoChars:"
     "mov ecx, 0;"
     "mov edx, 0;"
     "jmp Finish;"

    "Finish:"
      "mov %0, ecx;"
      "mov %1, edx;"

    ".att_syntax prefix;"
    :"=r" (x), "=r" (y)
    :"r" (s)
    :"eax", "ebx", "ecx", "edx"
  );

  printf("%d, %d \n", x, y);
  return 0;
}

显示预期结果(5, 10)。这意味着,匹配的正则表达式从第 5 个位置开始,长度为 10

首先,你对自己的要求有点不清楚。当我第一次阅读您的 post 时,看起来您正试图用汇编程序 () 编写一个完整的 "regex" 例程。但是仔细观察,您似乎真正在做的只是 "hardcoding" 这个在汇编程序中非常具体的正则表达式搜索。这种误解可能是这个问题没有得到任何回应的原因。

其次,你应该和 谈谈,他显然和你在同一个 class。也许你们两个可以分享笔记。

第三,有人应该和你的导师谈谈他的作业。使用 gcc 的 "inline asm" 来教授 asm 可能是最难的方法。他讨厌他的学生吗?我对他提供的 "template" 印象不深,(显然?)您不允许更改。我可以在这里看到至少六处我要更改的东西。

第四,你说正则表达式字符串“[ab][^r]+r”应该为"fqr b qabxx xryc pqr"打印出5, 10。我不确定那怎么可能。匹配从(从零开始)5 开始,但不在位置 10 结束:

          1         2
0123456789012345678901
fqr  b qabxx  xryc pqr
     ^         ^
   start      end

结尾是位置 15。匹配字符串 (b qabxx xr) 的长度为 11 个字符,因此显然您不是在寻找长度。第二个 'start' 出现在位置 8,第三个出现在位置 9,并且还有多个可能的端点。 None 其中解释了您应该在哪里获得“10”。我假设你的意思是 5, 15.

综上所述,处理 [ab][^r]+r 基本上分为 3 个部分:

  1. [ab] 查找 'a' 或 'b'。如果找不到字符串,则在遇到字符串结尾时出错并退出。
  2. [^r]+ 如果 (1) 后面的字母是 'r',转到 1.
  3. r 遍历字符串的其余部分并在下一个成功退出 'r',或者在字符串末尾错误退出。

如果您不明白为什么会有这些部分,请尝试使用 https://regex101.com/r/E3nI1F/1(它可以让您尝试各种正则表达式搜索字符串以查看它找到的内容)。

查看您当前的代码,我认为您没有正确处理 (2) 或 (3)(实际上,我认为您根本没有处理它们)。虽然我会在您的代码中更改其他内容,但也许调整应该等到事情正常工作。

考虑到这是家庭作业,我不想只 post 编写我的代码。如果你只是 copy/paste 我的作品,你不会学到任何东西。

如果您想在添加 2 和 3 的作业后编辑您的问题,我可以再次审核或提供更多建议。如果你post一份工作副本,我可以分享我的,我们可以比较它们。

------------ 编辑 1 --------------

my teacher does not seem to hate us

哦?考虑这段代码(你的简化版本):

asm volatile (
   "xor %0, %0;"
   "mov %1, %2"
   :"=r" (x), "=r" (y)
   :"r" (s));

看起来很简单,对吧?将 x 归零,然后将 s 复制到 y。但是,由于一些叫做 "early clobber" 的东西(参见 https://gcc.gnu.org/onlinedocs/gcc/Modifiers.html 上的 '&'),gcc 在优化时 可能 (不保证)会选择%0 和 %2(或者可能是 %1 和 %2)使用相同的寄存器。因此,当您将 %0 归零时,您也可以将 %2 归零。

这可以通过添加 & 符号来解决,以确保没有重叠:

:"=&r" (x), "=&r" (y)

但是你怎么会知道这个呢?知道这个细节并不能帮助你学习汇编程序。这只是关于 gcc 的内联 asm 工作原理的一个奇怪的怪癖。如果你正在编写一个实际的 asm 例程(这是我推荐的),你永远不需要知道这一点。

如果您使用符号名称,这不是更容易阅读吗?

asm volatile (
   "xor %[x], %[x];"
   "mov %[y], %[s]"
   : [x] "=&r" (x), [y] "=&r" (y)
   : [s] "r" (s));

觉得它更容易阅读。但这是另一件与汇编语言无关的事情。这只是一个关于如何在使用 gcc 时将内联 asm 推入 c 代码的技巧(你应该 almost never 做的事情)。

还有什么?此模板的其他一些问题:volatile 限定符不属于此处。它缺少 "cc" 破坏。还有 "memory" 破坏。你最终破坏了比你需要的更多的寄存器。哦,为什么不直接告诉人们使用 -masm=intel 编译并避免使用“.intel_syntax noprefix;”和“.att_syntax 前缀;”垃圾(还有更多 gcc 怪癖)。

使用汇编语言可以有用。我并不是要说它不是。但是尝试使用 gcc 的内联 asm 充满了怪癖。由于可以从 C 代码调用用纯汇编程序编写的函数,并且由于该方法有 none 个这些问题,我只能得出结论,你被迫这样做是因为你对 him/her 和(s)他讨厌你。

------------ 编辑 2 --------------

既然你已经 post 编辑了工作代码(假设你已经修复了 "arb r"),让我提供我的代码:

#include <stdio.h>

int main(int argc, char *argv[]) 
{
  const char *s = "fqr  b qabxx  xryc pqr"; // Succeeds with 5,11

  int x, y;

  // Assumes s is not NULL.
  // Return y=-1 on not found.

  asm volatile (
  ".intel_syntax noprefix\n\t"

     "lea ebx, [%2-1]\n\t"  // ebx is pointer to next character.
     "mov ecx, %2\n\t"      // Save this since we aren't using earlyclobber...
     "mov %1, -1\n"         // ...so at this point, %2 might be dead.

  // Note that ebx starts at s-1.

  "Phase1:\n\t"
     "inc ebx\n\t"
     "mov al, [ebx]\n\t" // Read next byte.

     "test al, al\n\t" 
     "jz Done\n\t"       // End of string.

     // Check for [ab]
     "cmp al, 'a'\n\t" 
     "je Phase2\n\t"

     "cmp al, 'b'\n\t"
     "jne Phase1\n"

     // Phase 2 - Found [ab], check for [^r]+
  "Phase2:\n\t"
     "mov al, byte ptr [ebx+1]\n\t"

     "test al, al\n\t" 
     "jz Done\n\t"     // End of string.

     "cmp al, 'r'\n\t"
     "je Phase1\n\t"   // Found [^r]+, go look for another [ab]

     "mov %0, ebx\n\t"

     // Found [ab], and no [^r]+.  Find r.
     "mov ebx, 1\n"

  "Phase3:\n\t"
     "mov al, [%0 + ebx]\n\t" // Read next byte.
     "inc ebx\n\t"

     "test al, al\n\t" 
     "jz Done\n\t"     // End of string.

     "cmp al, 'r'\n\t"
     "jne Phase3\n\t"

     // Found r.
     "sub %0, ecx\n\t" // Set (x)
     "mov %1, ebx\n"

  "Done:\n"

  ".att_syntax prefix"
  :"=r" (x), "=r" (y)
  :"r" (s)
  :"eax", "ebx", "ecx", "edx"
  );

  printf("%d, %d \n", x, y);
  return 0; 
}

它更短,不需要那么多寄存器(没有 edx)。虽然它可以再调整一点,但它是解决家庭作业问题的可靠方法。

如果让你改一下框架,可能会好一点:

   // Returns y = -1 if no regex match is found.

  __asm__ (
      // ---------------------------------
      // Phase1 - look for [ab]

      "mov %[x], %[s]\n"   // Pointer to next char to read

   "Phase1:\n\t"
      "mov al, [%[x]]\n\t" // Read next byte

      "test al, al\n\t" 
      "jz NotFound\n\t"    // Hit end of string

      "inc %[x]\n\t"

      "cmp al, 'a'\n\t" 
      "je Phase2\n\t"

      "cmp al, 'b'\n\t"
      "jne Phase1\n"

      // ---------------------------------
      // Phase2 - Found [ab], Check for [^r]+
   "Phase2:\n\t"

      // x is pointing to the byte after [ab]
      "mov al, [%[x]]\n\t"  // Read next byte.

      "test al, al\n\t" 
      "jz NotFound\n\t"     // Hit end of string

      "cmp al, 'r'\n\t"
      "je Phase1\n\t"  // Found [^r]+, go look for another [ab]

      // ---------------------------------
      // Phase3 - Found [ab], and no [^r]+.  Now find r.

      // x went 1 too far back in Phase1
      "dec %[x]\n\t"

      // We know there is 1 non-r character after [ab]
      "mov %[y], 1\n"

   "Phase3:\n\t"
      "mov al, [%[x] + %[y]]\n\t" // Read next byte.
      "inc %[y]\n\t"

      "test al, al\n\t" 
      "jz NotFound\n\t"     // End of string.

      "cmp al, 'r'\n\t"
      "jne Phase3\n\t"

      // Found +r.
      "sub %[x], %[s]\n\t"  // Set x to offset
      "jmp Done\n"

   "NotFound:\n\t"
      "mov %[y], -1\n"

   "Done:"

   : [x] "=&r" (x), [y] "=&r" (y)
   : [s] "r" (s)
   : "eax", "cc", "memory");

主要变化是:

  1. 假设代码是用 -masm=intel.
  2. 编译的
  3. "=r" 更改为 "=&r"。这保证 xys 最终都在不同的寄存器中。
  4. 使用符号名称。我们可以使用名称 %[x].
  5. 而不是将 x 称为 %0
  6. 由于此代码读取内存并修改标志,我添加了 "cc" 和 "memory" clobbers。
  7. 删除不需要的 volatile.

这破坏了更少的寄存器(只有 eax)。虽然使用寄存器不是 'bad'(没有它们很难做很多事情),但您在 asm 中保留使用的越多,编译器在调用您的函数之前释放这些寄存器的工作就越多代码。由于 xys 在寄存器中 已经 (由于 "r"),使用它们可以简化代码。

我意识到这不符合 'answer' 的条件,因为作业要求您使用教师提供的特定格式。然而,由于我觉得使用内联汇编是一种糟糕的学习 asm 的方法,所以我想展示一下如果你将它写成纯 asm 会是什么样子。试图把这个塞进另一个(已经很长的)答案中似乎不太合适。

相反,我建议使用 2 个文件。第一个是纯C代码:

#include <stdio.h>

extern int __cdecl DoRegEx(const char *s, int *startpos);

int main(void) 
{
  const char *s = "fqr  b qabxx  xryc pqr";
  int startpos, len;

  len = DoRegEx(s, &startpos);

  printf("%d, %d\n", startpos, len);
  return 0; 
}

read/maintain 这比您最终使用内联汇编要容易得多。但更重要的是,这是 asm 文件:

# foo2.s - Searches for regex "[ab][^r]+r" in string
#
# Called from c with:
#
#    extern int __cdecl DoRegEx(const char *s, int *startpos);
#
# On input:
#
#   [esp+4] is s
#   [esp+8] is pointer to startpos.
#
# On output:
#
#   startpos is the (zero based) offset into (s) where match begins.
#   Length of match (or -1 if match not found) is returned in eax.
#
# __cdecl allows the callee (that's us) to modify any of EAX, ECX, 
# and EDX. All other registers must be returned unchanged.
#

# Use intel syntax
.intel_syntax noprefix

# export our symbol (note __cdecl prepends an underscore to names).
.global _DoRegEx

# Start code segment
.text

_DoRegEx:
   mov ecx, [esp+4] # Load pointer to (s)

Phase1:
   mov dl, [ecx]    # Read next byte

   test dl, dl 
   jz NotFound      # Hit end of string

   inc ecx          # Move to next byte

   cmp dl, 'a'      # Check for 'a'
   je Phase2

   cmp dl, 'b'      # Check for 'b'
   jne Phase1

   ... blah blah blah ...

   mov edx, [esp+8]          # get pointer to startpos
   mov DWORD PTR [edx], ecx  # write startpos

   ret

您可以使用 gcc -m32 -o foo.exe foo1.c foo2.s.

同时编译+link 两个文件

如果您最终以使用汇编程序为生,那么它看起来比您使用 gcc 的扩展 asm(即使在最好的时候也很丑陋)所看到的更可能是这样的。它还涉及常见的现实世界概念,例如从堆栈读取参数、保存寄存器和使用汇编程序指令(.text、.global 等)。将其内联到 C 中时,这些内容大部分对您是隐藏的,但它们是使用和理解汇编语言的基本组成部分。

FWIW.

PS 你的代码运行了吗?如果其他答案提供了足够的信息来创建您的程序,请不要忘记 'accept' 它。如果您再次遇到困难,请编辑您的原始 post 以添加您当前的代码,并包括对仍然无法正常工作的部分的描述。