当前位置: 首页 > 知识库问答 >
问题:

Brainfuck解释器不处理某些代码,而所有指令都被删除了

劳昊明
2023-03-14

我在汇编时做了一个脑力操翻译(在

使用此代码:

++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-]>>.>---.+++++++..+++.>>.<-.<.+++.------.--------.>>+.>++.

它打印Hello World!没有问题,请注意它几乎拥有您需要的一切:嵌套循环,您可以执行的所有命令(除了)。它仍然工作得非常好。

然而,当使用这段代码时(预期的输出也是Hello World!就像第一段代码一样):

--<-<<+[+[<+>--->->->-<<<]>]<<--.<++++++.<<-..<<.<+.>>.>>.<<<.+++.>>.>>-.<<<+.

它打印“n|1f14{g”(没有任何seg错误或非法指令)。我摸不着头脑,为什么这个代码不起作用,而另一个代码工作得非常好。我想我已经处理了所有可能的指令和一些必须硬编码的异常(嵌套循环)。在编写Brainfuck解释器时,我还应该记住其他一些警告吗?嵌套循环似乎是唯一的警告,这些工作得非常好。我尝试过调试,但也没有帮助我。

这是我的代码:

.global main

format_str: .asciz "--<-<<+[+[<+>--->->->-<<<]>]<<--.<++++++.<<-..<<.<+.>>.>>.<<<.+++.>>.>>-.<<<+."

main:
    pushq %rbp
    movq %rsp, %rbp

    movq $format_str, %rdi
    call brainfuck

    movq $0, %rdi
    popq %rbp
    call exit
    
brainfuck:
    pushq %rbp
    movq %rsp, %rbp
    pushq %rbx
    movq $1, %r15       # use as loop counter
    
    movq %rdi, %rbx     # move the pointer to the first char in the string to %rbx
    movq $100000, %rdi  # allocate 100000 bytes in the heap
    call malloc         # call the actual malloc function
    movq %rax, %r12     # move the data pointer to the allocated bytes to r12
    addq $50000, %r12   # add 50000 to the location so we start in the middle. So when starting you can also go to "negative" cells
    sub $1, %rbx

.back:
    add $1, %rbx        # iterate one char over the string we have
.findVal:
    movzbl (%rbx),%eax  # move (%rbx) into %eax, only accesses memory once, speeding up the tests.
    cmp  $0,(%rbx)      # test for end of string
    je   .end
    cmp  $'+', %al      # test for plus
    je   .plus
    cmp  $'-', %al      # test for minus
    je   .minus
    cmp  $'>', %al      # test for right
    je   .right
    cmp  $'<', %al      # test for left
    je   .left
    cmp  $'[', %al      # test for opening bracket
    je   .openloop 
    cmp  $']', %al      # test for closing bracket
    je   .closeloop
    cmp  $'.', %al      # test for dot
    je   .dprint
    cmp  $',', %al      # test for comma
    je   .cinput
    jmp  .back          # if none match then it is an unsupported command, so iterate once over the string and jump back
    
    
.plus:
    add $1,(%r12)       # add 1 to the cell the data pointer (r12) is pointing at
    jmp .back
    
.minus:
    sub $1,(%r12)       # subtract 1 of the cell the data pointer (r12) is pointing at
    jmp .back
    
.right:
    add $1,%r12         # add 1 to the memory location of the data pointers, (hence moving up 1 cell)
    jmp .back
    
.left:
    sub $1,%r12         # subtract 1 to the memory location of the data pointers, (hence moving down 1 cell)
    jmp .back
    
.openloop:
    cmpb $0,(%r12)      # if the current cell we are pointing at is 0, jump to closing bracket
    je .jumpclose
    pushq %rbx          # save the opening bracket location on the stack (to deal with nested loops)
    jmp .back
    
.closeloop:
    cmpb $0,(%r12)      # if the current cell we are pointing at is 0, exit the loop
    je .endloop
    movq (%rsp), %rbx   # if it's not 0, move the top of the stack to the rbx, so we start back at the last pushed opening bracket
    jmp .back    
.endloop:
    addq $8,%rsp        # if it is the end of a loop (the cell at (%r12) is 0), then we add 8 to the stack pointer, so that the next jumping position (if there is one) is at (%r12)
    jmp .back

.inc_r15:    
    add $1, %r15        # increase nested loop counter
.jumpclose:             # finds the corresponding closing bracket
    add $1, %rbx        # go to next char
    movzbl (%rbx),%eax  # move rbx to eax so we access memory less often
    cmp  $']', %al      # check for closing loop
    je .closeloopfound
    cmp  $'[', %al      # check for opening loop
    je .inc_r15         # if there is another nested loop increase the loop counter
    jmp .jumpclose      # if none of those then simply jump back to iterate to the next char
.closeloopfound:
    sub  $1, %r15       # when encountering a closing loop, subtract 1 from the loop counter
    cmpq $0, %r15       # r15 is intiated with 1 since when we enter a loop we also have 1 opening bracket, if its equal to 0 after the last subtraction that means we found the correct bracket
    jne  .jumpclose     # if it's not 0, then jump back to jumpclose to continue finding the correct bracket
    mov  $1, %r15       # move 1 back into r15 to take care of the next time we have to skip a loop
    jmp  .back          # jump back
    
    
.dprint:
    movq    $1, %rax                     # perform syscall 1 which is sys_write
    movq    $1, %rdi                     # write to stdout
    movq    %r12, %rsi                   # use the char that %r12 is pointing to, which is stored as ascii
    movq    $1, %rdx                     # write 1 byte (amount of byte)
    syscall                              # perform the system_write (print) with syscall

    jmp .back
    
.cinput:
    movq    $0, %rax                     # sys_read call number 
    movq    $1, %rdi                     # read from stdin
    movq    %r12, %rsi                   # print the ascii value of whatever %r12 is pointing to
    movq    $1, %rdx                     # write 1 char
    syscall                              # perform the system_read with syscall
    
    jmp .back
    
.end:                                    # exiting the function 
    popq %rbx                            # restore %rbx
    popq %rbp                            # restore %rbp
    ret                                  # return to the proper return adress

为了清楚起见,我添加了一些注释。我只是完全迷失在代码出错的地方,我使用的是一个在线编译器,以前从未让我失望过。所以我不认为这是问题所在。

编辑:在进一步搜索之后,我发现了一个我错过的“警告”。我不是在进入循环时检查0,我只是在退出循环时检查0,就像一个真正的蠢驴。我已经更新了代码并添加了这个功能,幸运的是一些程序现在可以工作了(比如Daniel Cristofani的Sierpinski)。但有些程序仍然失败(上面的程序仍然失败)。现在我真的不知道该怎么办了。

共有1个答案

董子平
2023-03-14

经过几个小时的搜索,我已经修复了错误。在我正在做的加减部分

add $1, (%r12) 

错误是我本应该这样做:

addb $1, (%r12) 

如果我仔细想想,这是有道理的,但是我不希望整个代码因为这样的事情而无法运行。在这一点上,有些代码确实起作用了,比如mandelbrot(最复杂的代码之一)。我一直看错地方了。

 类似资料:
  • 众所周知,我们可以像ProjectLombook的工作人员一样,使用自定义注释和Java注释处理器自动生成代码。但是我们可以从编译的源代码中删除带注释的代码吗? 我试着在网上搜索它,但只出现了“生成代码”主题和“如何生成带有一个注释的服务器”教程。当我在寻找从prod应用程序“编译”调试消息的方法时,我想到了这一点。我可以理解,拥有调试/测试和生产代码不是一个好的实践,但有时需要保持简单。我认为这

  • 我对此真的很困惑。如果我以编程方式删除Firest集合中的所有文档,集合本身也会被删除: 但是,如果我在线使用Firebase控制台删除所有文档,我并不总是看到集合被删除!我从字面上删除了集合中的所有文档,但它仍然在控制台中。我可以立即在集合中创建另一个文档。但有时集合确实会消失......?! 我读了一些帖子,比如删除Firestore收藏中的所有文件,但没有看到任何解释这一点的内容。 因为一个

  • 本文向大家介绍git代码不变,删除所有commit记录,包括了git代码不变,删除所有commit记录的使用技巧和注意事项,需要的朋友参考一下 把旧项目提交到Git服务器上,会有很多以前的commit记录。 由于各种各样的原因,不希望在新的Git服务器上显示这些commit信息。 那如何删除这些commit记录,形成一个全新的仓库,并且保持代码不变呢? 参考资料: how to delete al

  • 我有这个单声道代码: 当validate方法抛出ValidationException时,将同时调用handleValidation异常和handleException。我只希望调用handleValidationException。为什么会发生这种情况?如何防止handleException被调用?

  • 像第七章描述的,指令cache是8k或16k。 如果代码的要害部位无法完全放进指令cache,那么可以考虑减小代码尺寸。 一般32位代码比16位代码大,因为32位代码的地址和数据常量是4个字节,16位代码是2个字节。 然而,16位代码有一些其它的惩罚诸如前缀的惩罚,同时访问邻近的字带来的问题(前述10.2章)。 减小代码尺寸的其它方法在下面讨论。 如果跳转地址,数据地址和数据常量在-128到127

  • 我无法理解口译员和JIT之间的区别。例如,从这个答案中: JVMJava虚拟机-运行/解释/翻译字节码到本机代码 JIT是实时编译器——在运行时将给定的字节码指令序列编译为机器代码,然后在本地执行。它的主要目的是对性能进行重大优化。 两者都产生本机机器代码。然后,从另一个答案来看: 解释器为每条指令动态生成和执行机器代码指令,而不管之前是否执行过。JIT缓存以前已解释为机器代码的指令,并重用这些本