100개 언어 Speedrun: 77회: Jasmin의 JVM 조립

16108 단어 assemblyjvmjava
JVM(Java Virtual Machine)의 작동 방식이 이상합니다.우선 자바나 Kotlin 같은 소스 코드가 있습니다.JVM 프로그램 세트에 중간 형식으로 컴파일됩니다.그리고 그것을 실제 실행 가능한 기계 코드로 컴파일한다.
어느 정도에 JVM 어셈블리 단계는 불필요하다. 예를 들어 안드로이드는 서로 다른 흐름을 사용하고 심지어 버전마다 같은 흐름을 사용하지 않는다.
한 마디로 하면 일반적인 JVM의 고전적인 JVM 프로그램 집합이 어떤 모양인지 살펴보자.JVM에는 사람이 읽을 수 있는 어셈블리를 위한 도구가 포함되어 있지 않으므로 Jasmin을 사용하겠습니다.또 다른 JVM 어셈블러는 약간 다르지만, 이러한 차이는 우리의 간단한 용례에 있어서는 중요하지 않다.

안녕, 세상!


JVM에 대한 기초 지식을 알고 있다면 자바든 다른 JVM 언어든 도움이 될 것입니다.만약 그렇지 않다면, 나는 한 걸음 한 걸음 모든 것을 설명하려고 시도할 것이다.
우리 너부터 시작하자, 세상!다음은 Hello.j입니다.
.class public Hello
.super java/lang/Object

.method public static main([Ljava/lang/String;)V
    .limit locals 1
    .limit stack 2

    getstatic java/lang/System/out Ljava/io/PrintStream;
  ldc "Hello, World!"
    invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V

    return

.end method
jasminHello.class으로 컴파일할 수 있습니다.
$ jasmin Hello.j
Generated: Hello.class
그런 다음 java에 클래스 이름을 전달하여 실행합니다.
$ java Hello
Hello, World!
혼동을 피하기 위해 java 명령은 자바 언어와 상관없이 자바 가상 기기와만 관련이 있다.javac 명령은 Java 언어를 처리합니다.
다음은 Hello.j 파일의 구조를 살펴보겠습니다.
  • 코드는 하나의 클래스에 필요합니다. 클래스는 보통 파일 이름과 일치해야 합니다. Hello.j에 대해 우리는 먼저 .class public Hello을 정의합니다.
  • 은 모든 클래스에 하나의 슈퍼클래스가 있는데 기본적인 슈퍼클래스는 Object이고 내부는 java/lang/Object이라고 부른다.
  • 그리고 우리는 main 방법을 정의했습니다. 이것은 명령줄에서 클래스를 실행할 때 실행하는 방법입니다..method public static methodname ... .end method은 방법 정의로 방법 내부에 각종 지령이 있는데 방법이
  • 을 운행할 때 이 지령들은 운행할 것이다
  • 다양한 방법이 있는 경우 .method ... .end method 블록
  • 성명 방법 .method public static은 그것이 유형 방법이고 특정한 실례에 귀속되지 않는다는 것을 의미한다
  • 이름 개편


    지금까지 이것은 일리가 있다.네가 물어볼 수 있는 첫 번째 질문은 이 이름들이 도대체 무엇인지이다.
  • main([Ljava/lang/String;)V
  • Ljava/io/PrintStream;
  • java/io/PrintStream/println(Ljava/lang/String;)V
  • 이것이 바로'이름을 흐트러뜨리다'다.자바와 다른 일부 언어에서는 유형이 다르기만 하면 같은 이름의 함수나 방법이 여러 개 있을 수 있다.
    나는 가장 간단한 방법이 무엇인지 파손된 이름을 찾아낼 수 없다.
    예를 들어, main([Ljava/lang/String;)Vint main(java.lang.String[]);을 나타냅니다.
  • 명칭 main 우선
  • 그리고 ( 시작 매개 변수 목록
  • [
  • 의 수조를 나타낸다
  • Ljava/lang/String;java.lang.String 유형의 대상을 나타낸다. - 분호는 명칭이
  • 에서 끝난 위치를 나타낸다.
  • ) 닫기 매개 변수 목록
  • Vvoid으로 되돌아간다고 하는데 이것은
  • 이라고 할 수 없다
    명칭 혼란은 우리가 사용하는 언어에 달려 있다.다른 JVM 언어는 자바가 지원하는 유형에 대해 자바와 호환되는 변경 사항을 사용하지만, 자바가 필요로 하는 추가 기능에 대해서는 자신의 이름 변경 방안을 제시해야 한다.JVM에서 파괴된 이름은 모두 간단한 문자열일 뿐이지만, Jasmin은 모든 내용을 정확하게 설정하기 위해 자바 이름 파괴 규칙을 따라야 한다.

    안에서 안녕, 세상!메서드


    네, 이 방법을 보여 주세요.
    우선 .limit개의 정의부터 시작하겠습니다.
        .limit locals 1
        .limit stack 2
    
    이 매개 변수는 함수에 몇 개의 국부 변수가 있는지, 그리고 최대 창고 공간이 얼마나 필요한지 지정합니다.JVM은 컨테이너 기계이기 때문에 대부분의 지령은 물건을 컨테이너에 밀어넣거나 컨테이너에서 꺼내 각종 조작을 수행한다.
    Jasmin이 이 계산을 자동으로 완성할 수 있다고 믿을 이유가 있습니다. 만약 당신이 지정한 숫자가 너무 낮다면 JVM은 클래스를 불러오는 것을 거부할 것입니다.
    이제 스택에서 값을 푸시해야 합니다.
        getstatic java/lang/System/out Ljava/io/PrintStream;
      ldc "Hello, World!"
    
    getstatic은 특정 유형의 정적 값(이 예는 java.lang.System.out 유형의 java.io.PrintStream)을 가져오고 이 값에 대한 인용을 창고 꼭대기로 보냅니다.
    그리고 ldc "Hello, World!"에서 상수 값을 가져와 인용을 창고 맨 위로 가져옵니다.
    그리고 우리는 방법이라고 부른다.
        invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V
    
    invokevirtualvirtual 방법을 사용하는데 이 방법은 이불류에 무거운 짐을 실을 수 있다.우리는 전달하고 있는 방법의 전체 이름을 지정해야 한다.JVM은 유형 서명에 따라 스택 상단에서 사용할 매개변수를 알고 있습니다.
    마지막:return은 이 방법에서 되돌아온다.
    부차적인 기술 문제에 대해 모든 종류, 방법, 유형 이름, 상수 문자열 등은 바이트 코드가 아닌 상수 탱크에 저장된다.바이트 코드는 실제로 "상수 탱크의 상수 #7"과 같은 것을 가리킨다.그러나 이렇게 쓰면 지루하기 때문에 Jasmin은 적어도 우리를 위해 이렇게 많은 것을 해 주었다.

    순환하다


    일정 횟수를 순환하고 숫자를 출력하는 약간 복잡한 일을 합시다.
    다음은 플롯 값 1부터 10까지의 주기입니다.
    .class public Loop
    .super java/lang/Object
    
    .method public static main([Ljava/lang/String;)V
        .limit locals 1
        .limit stack 2
    
      iconst_1 ; push value 1 on stack
        istore 0 ; save that to local variable #0
    
    loop:
    
        iload 0   ; push local #0 to stack
        bipush 10 ; push byte value 10 on stack
        if_icmpgt end_loop ; if local #0 > 10, goto end_loop
    
        getstatic java/lang/System/out Ljava/io/PrintStream; ; push System.out on stack
      iload 0 ; load local #0
        invokevirtual java/io/PrintStream/println(I)V ; print it
    
        iinc 0 1 ; increase local variable #0 by 1
    
        goto loop
    
    end_loop:
    
        return
    
    .end method
    
    테스트:
    $ jasmin Loop.j
    Generated: Loop.class
    $ java Loop
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
    새 운영 코드는 다음과 같습니다.
  • iconst_1 - 1을 스택으로 밀어 넣습니다. - 고유의 작동 코드가 있는
  • 과 매우 흔한 숫자입니다.
  • bipush 10 - 스택에서 10 - bipush에서 8비트 큰 숫자, sipush에서 16비트 푸시 - 더 큰 숫자는 항상
  • 에서 로드됩니다.
  • istore 0iload 0 - 로컬 변수 저장 및 로드
  • iinc 0 1 - 로컬 변수 #0을 1로 늘려 마이너스로
  • 감소
  • goto label - 태그
  • 으로 건너뛰기
  • if_icmpgt label - 처음 두 값이 서로 크면(icmpgt의 경우 Integer CoMPare Greater Than)
  • 으로 이동합니다.
  • 은 우리가 호출한 방법이 java/io/PrintStream/println(Ljava/lang/String;)V(인쇄 문자열)에서 java/io/PrintStream/println(I)V(인쇄 int)으로 바뀌었다는 것을 주의하십시오. - JVM의 경우 이러한 방법은 완전히 독립적이고 관련이 없습니다.모든 JVM 언어에서 우리는 println(...)을 말할 수 있는데 이것은 우리가 가리키는 것이 어떤 것인지 확실히 알 수 있다.그러나 이는 JVM이
  • 에 도달하기 전에 잘못된 뜻을 없애야 합니다.

    <unk>거리다


    우리는 현재 FizzBuzz을 만드는 데 필요한 모든 것을 가지고 있다.
    .class public FizzBuzz
    .super java/lang/Object
    
    .method public static main([Ljava/lang/String;)V
        .limit locals 1
        .limit stack 2
    
      iconst_0 ; push value 0 on stack
        istore 0 ; save that to local variable #0
    
    loop:
    
        iinc 0 1 ; increase local variable #0 by 1
    
        iload 0   ; push local #0 to stack
        bipush 100 ; push byte value 10 on stack
        if_icmpgt end_loop ; if local #0 > 100, goto end_loop
    
        iload 0
        bipush 15
        irem
        ifeq fizzbuzz
    
        iload 0
        iconst_5
        irem
        ifeq buzz
    
        iload 0
        iconst_3
        irem
        ifeq fizz
    
    print_number:
    
        getstatic java/lang/System/out Ljava/io/PrintStream; ; push System.out on stack
      iload 0 ; load local #0
        invokevirtual java/io/PrintStream/println(I)V ; print it
        goto loop
    
    fizz:
    
        getstatic java/lang/System/out Ljava/io/PrintStream;
      ldc "Fizz"
        invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V
        goto loop
    
    buzz:
    
        getstatic java/lang/System/out Ljava/io/PrintStream;
      ldc "Buzz"
        invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V
        goto loop
    
    fizzbuzz:
    
        getstatic java/lang/System/out Ljava/io/PrintStream;
      ldc "FizzBuzz"
        invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V
        goto loop
    
    end_loop:
    
        return
    
    .end method
    
    우리가 필요로 하는 유일한 새로운 조작은 다음과 같다.
  • iconst_X 운영 코드를 5로 늘리면 최적화된 iconst_3iconst_5을 사용할 수 있지만 더 큰 숫자에는 bipush 15이 필요합니다.
  • irem % 운영
  • ifeq과 기타 ifXX의 조작 코드와 0의 비교, if_icmpXX의 조작 코드는 두 개의 정수치인
  • 을 비교한다.

    피보나치


    페보나치를 정의하고 public static int fib(int n) 함수의 등가물로 계산합시다.
    .class public Fib
    .super java/lang/Object
    
    .method public static fib(I)I
        .limit stack 3
      iload_0
        iconst_2
        if_icmple small_fib
    
    big_fib:
        iload_0
        iconst_1
        isub
        invokestatic Fib/fib(I)I  ; push fib(i-1) to stack
    
        iload_0
        iconst_2
        isub
        invokestatic Fib/fib(I)I  ; push fib(i-2) to stack
    
        iadd
        ireturn ; return fib(i-1) + fib(i-2)
    
    small_fib:
        iconst_1
        ireturn ; return 1
    .end method
    
    .method public static main([Ljava/lang/String;)V
        .limit locals 1
        .limit stack 2
    
      iconst_1 ; push value 1 on stack
        istore 0 ; save that to local variable #0
    
    loop:
    
        iload 0   ; push local #0 to stack
        bipush 30 ; push byte value 10 on stack
        if_icmpgt end_loop ; if local #0 > 10, goto end_loop
    
        getstatic java/lang/System/out Ljava/io/PrintStream;
        ldc "fib("
        invokevirtual java/io/PrintStream/print(Ljava/lang/String;)V ; print "fib("
    
        getstatic java/lang/System/out Ljava/io/PrintStream;
      iload 0 ; load local #0
        invokevirtual java/io/PrintStream/print(I)V ; print i
    
        getstatic java/lang/System/out Ljava/io/PrintStream;
        ldc ")="
        invokevirtual java/io/PrintStream/print(Ljava/lang/String;)V ; print ")="
    
        getstatic java/lang/System/out Ljava/io/PrintStream; ; push System.out on stack
      iload 0 ; load local #0
        invokestatic Fib/fib(I)I
        invokevirtual java/io/PrintStream/println(I)V ; print fib(i)
    
        iinc 0 1 ; increase local variable #0 by 1
    
        goto loop
    
    end_loop:
    
        return
    
    .end method
    
    $ jasmin Fib.j
    Generated: Fib.class
    $ java Fib
    fib(1)=1
    fib(2)=1
    fib(3)=2
    fib(4)=3
    fib(5)=5
    fib(6)=8
    fib(7)=13
    fib(8)=21
    fib(9)=34
    fib(10)=55
    fib(11)=89
    fib(12)=144
    fib(13)=233
    fib(14)=377
    fib(15)=610
    fib(16)=987
    fib(17)=1597
    fib(18)=2584
    fib(19)=4181
    fib(20)=6765
    fib(21)=10946
    fib(22)=17711
    fib(23)=28657
    fib(24)=46368
    fib(25)=75025
    fib(26)=121393
    fib(27)=196418
    fib(28)=317811
    fib(29)=514229
    fib(30)=832040
    
    한 걸음 한 걸음 살펴보자.
  • 주함수는 하나의 순환이 있는데 그 중에서 각종 print(String), print(int)println(int)
  • 을 호출한다
  • invokestatic Fib/fib(I)I 호출 클래스 int fib(int) 중의 정적 함수 Fib, 우리는 현재
  • fib 내부에서 invokestatic Fib/fib(I)I을 차례로 호출합니다
  • iload_0은 방법의 첫 번째 파라미터를 창고로 전송한다(파라미터는 국부 변수가 되기 때문에 같은 숫자를 공유한다)
  • iaddisub의 정수 가감
  • ireturn 정수
  • 반환

    Java 어셈블러


    유행하는 관련 소프트웨어 패키지는 자바 어셈블러 javap으로 .class 파일을 JVM 프로그램 집합으로 변환할 수 있다.불행하게도 javapjasmin은 세부적으로 일치하지 않는다.
    $ javap -c Fib.class
    Compiled from "Fib.j"
    public class Fib {
      public static int fib(int);
        Code:
           0: iload_0
           1: iconst_2
           2: if_icmple     19
           5: iload_0
           6: iconst_1
           7: isub
           8: invokestatic  #21                 // Method fib:(I)I
          11: iload_0
          12: iconst_2
          13: isub
          14: invokestatic  #21                 // Method fib:(I)I
          17: iadd
          18: ireturn
          19: iconst_1
          20: ireturn
    
      public static void main(java.lang.String[]);
        Code:
           0: iconst_1
           1: istore        0
           3: iload         0
           5: bipush        30
           7: if_icmpgt     54
          10: getstatic     #15                 // Field java/lang/System.out:Ljava/io/PrintStream;
          13: ldc           #32                 // String fib(
          15: invokevirtual #24                 // Method java/io/PrintStream.print:(Ljava/lang/String;)V
          18: getstatic     #15                 // Field java/lang/System.out:Ljava/io/PrintStream;
          21: iload         0
          23: invokevirtual #29                 // Method java/io/PrintStream.print:(I)V
          26: getstatic     #15                 // Field java/lang/System.out:Ljava/io/PrintStream;
          29: ldc           #14                 // String )=
          31: invokevirtual #24                 // Method java/io/PrintStream.print:(Ljava/lang/String;)V
          34: getstatic     #15                 // Field java/lang/System.out:Ljava/io/PrintStream;
          37: iload         0
          39: invokestatic  #21                 // Method fib:(I)I
          42: invokevirtual #9                  // Method java/io/PrintStream.println:(I)V
          45: iinc_w        0, 1
          51: goto          3
          54: return
    }
    
    보시다시피 javap은 demangled 이름을 사용합니다. 이것은 디지털 현식 인용 상수지를 사용하고 일부 조작 코드는 다릅니다. (예: iinc_w 0, 1iinc 0 1)

    인간


    만약 작은 종류를 정의하지 않는다면, 이 회를 끝내는 것은 무의미하다.이를 위해 우리는 두 문자열 필드(Person, name)와 surname 방법을 포함하는 toString을 정의하기만 하면 된다.또한 정적 main을 사용하여 테스트를 수행합니다.
    나는 코드에 주석을 추가했다.비정상적인 방법의 경우 this이 첫 번째 추가 매개변수로 전달되므로 JVM의 관점에서 보면 로컬 변수는 다음과 같습니다.
  • 로컬 0-this
  • 로컬 1-첫 번째 매개 변수
  • 로컬 2 - 두 번째 매개 변수
  • 국부3-첫 번째 국부변수
  • 국부4-두 번째 국부변수
  • .class public Person
    .super java/lang/Object
    
    .field public name Ljava/lang/String;
    .field public surname Ljava/lang/String;
    
    .method public <init>(Ljava/lang/String;Ljava/lang/String;)V
      .limit locals 4
      .limit stack 4
      ; local 0 - this
      ; local 1 - argument name
      ; local 2 - argument surname
    
      ; call super this.<init>();
      aload_0
      invokespecial java/lang/Object/<init>()V
    
      ; this.name = argument_name
      aload_0
      aload_1
      putfield Person/name Ljava/lang/String;
    
      ; this.surname = argument_surname
      aload_0
      aload_2
      putfield Person/surname Ljava/lang/String;
    
      return
    .end method
    
    .method public toString()Ljava/lang/String;
      .limit locals 4
      .limit stack 4
      ; local 0 - this
    
      ; push this.name
      aload_0
      getfield Person/name Ljava/lang/String;
    
      ; push " "
      ldc " "
    
      ; call String.concat, getting: this.name + " "
      invokevirtual java/lang/String/concat(Ljava/lang/String;)Ljava/lang/String;
    
      ; push this.surname
      aload_0
      getfield Person/surname Ljava/lang/String;
    
      ; call String.concat, getting: this.name + " " + this.surname
      invokevirtual java/lang/String/concat(Ljava/lang/String;)Ljava/lang/String;
    
      areturn
    .end method
    
    .method public static main([Ljava/lang/String;)V
        .limit locals 4
        .limit stack 4
    
      ; local Person a = new Person("Alice", "Smith")
      new Person
      dup
      ldc "Alice"
      ldc "Smith"
      invokespecial Person/<init>(Ljava/lang/String;Ljava/lang/String;)V
      astore_1
    
        getstatic java/lang/System/out Ljava/io/PrintStream;
    
      ; push a.toString()
      aload_1
      invokevirtual Person/toString()Ljava/lang/String;
    
        invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V
    
        return
    
    .end method
    
    $ jasmin Person.j
    Generated: Person.class
    $ java Person
    Alice Smith
    

    JVM 어셈블리를 사용해야 합니까?


    그것은 인류가 사용할 수 있는 것이고, 심지어는 일반적인 조립보다 적기 때문에 절대로 그렇지 않다.
    또 하나의 문제는 일반적인 어셈블리나 LLVM 어셈블리와 달리 후자는 완전히 지원되는 표준 형식이 있다. Jasmin은 제3자 프로그램이고 서로 다른 JVM 어셈블리와 반어셈블리 프로그램은 여러 가지 측면에서 차이가 있다.Krakatau과 같은 업데이트된 어셈블러와 반어셈블러도 있습니다. 한번 해 보십시오.Krakatau의 구문은 Jasmin 또는 javap과 다릅니다.
    JVM을 위한 새로운 언어를 개발하는 경우 JVM의 작동 방식을 이해하는 데 도움이 될 수 있지만 그 뿐입니다.
    JVM 어셈블리에 익숙한 또 다른 방법은 GodBolt compiler site을 사용하지만, 언어 (자바, Kotlin 등) 만 컴파일하고 출력에서 javap을 실행하기 때문에 로컬에서 실행할 수 있습니다.

    비밀 번호


    All code examples for the series will be in this repository .
    Code for the JVM Assembly with Jasmin episode is available here .

    좋은 웹페이지 즐겨찾기