<span id="7ztzv"></span>
<sub id="7ztzv"></sub>

<span id="7ztzv"></span><form id="7ztzv"></form>

<span id="7ztzv"></span>

        <address id="7ztzv"></address>

            原文地址:http://drops.wooyun.org/binary/6926

            54.7 線性同余偽隨機數生成器


            這次來看一個簡單的偽隨機函數生成器,之前我在書中提到過一次。

            #!java
            public class LCG
            {
                public static int rand_state;
                public void my_srand (int init)
                {
                    rand_state=init;
                }
                public static int RNG_a=1664525;
                public static int RNG_c=1013904223;
            
                public int my_rand ()
                {
                    rand_state=rand_state*RNG_a;
                    rand_state=rand_state+RNG_c;
                    return rand_state & 0x7fff;
                }
            }
            

            在上面的代碼中我們可以看到開始的地方有兩個類字段被初始化。不過java究竟是如何進行初始化的呢,我們可以通過javap的輸出看到類構造的方式。

            static {};
            flags: ACC_STATIC
            Code:
            stack=1, locals=0, args_size=0
            0: ldc #5 // int 1664525
            2: putstatic #3 // Field RNG_a:I
            5: ldc #6 // int 1013904223
            7: putstatic #4 // Field RNG_c:I
            10: return
            

            從上面的代碼我們可以直觀的看出變量如何被初始化,RNG_a和iRNG_C分別占用了第三以及第四儲存位,并使用puststatic指令將常量put進儲存位置。

            下面的my_srand()函數將輸入值存儲到rand_state中;

            public void my_srand(int);
            flags: ACC_PUBLIC
            Code:
            stack=1, locals=2, args_size=2
            0: iload_1
            1: putstatic #2 // Field ?
            ? rand_state:I
            4: return
            

            iload_1 取得輸入值并將其壓入棧。但為什么不用iload_0? 因為函數中可能使用了類字段,所以這個變量被作為第0個參數傳遞給了函數,我們可以看到rand_state字段在類中占用第二個儲存位。之前的putstatic會從棧頂復制數據并且將其壓入第二儲存位。

            現在的my_rand():

            public int my_rand();
            flags: ACC_PUBLIC
            Code:
            stack=2, locals=1, args_size=1
            0: getstatic #2 // Field ?
            ? rand_state:I
            3: getstatic #3 // Field RNG_a:I
            6: imul
            7: putstatic #2 // Field ?
            ? rand_state:I
            10: getstatic #2 // Field ?
            ? rand_state:I
            13: getstatic #4 // Field RNG_c:I
            16: iadd
            17: putstatic #2 // Field ?
            ? rand_state:I
            20: getstatic #2 // Field ?
            ? rand_state:I
            23: sipush 32767
            26: iand
            27: ireturn
            

            它僅是加載了所有對象字段的值。并且用putstatic指令對rand_state的值進行更新。

            因為之前我們通過putstatic指令將rand_state的值丟棄,所以在20行的位置,再次加載rand_state值。這種方式其實效率不高,不過我們還是要承認jvm在某些地方所做的優化還是很不錯的。

            54.8 條件跳轉


            我們來舉個條件跳轉的栗子:

            #!java
            public class abs
            {
                public static int abs(int a)
                {
                    if (a<0)
                        return -a;
                    return a;
                }
            }
            

            public static int abs(int);
            flags: ACC_PUBLIC, ACC_STATIC
            Code:
            stack=1, locals=1, args_size=1
            0: iload_0
            1: ifge 7
            4: iload_0
            5: ineg
            6: ireturn
            7: iload_0
            8: ireturn
            

            上面代碼中ifge指令的作用是:當棧頂的值大于等于0的時候跳轉到偏移位7,需要注意的是,任何的ifXX指令都會將棧中的值彈出用于進行比較。

            現在來看另外一個例子

            #!java
            public static int min (int a, int b)
            {
                if (a>b)
                    return b;
                return a;
            }
            

            我們得到的是:

            public static int min(int, int);
            flags: ACC_PUBLIC, ACC_STATIC
            Code:
            stack=2, locals=2, args_size=2
            0: iload_0
            1: iload_1
            2: if_icmple 7
            5: iload_1
            6: ireturn
            7: iload_0
            8: ireturn
            

            if_icmple會從棧中彈出兩個值進行比較,如果第二個小于或者等于第一個,那么跳轉到偏移位7.

            我們看另一個max函數的例子:

            #!java
            public static int max (int a, int b)
            {
                if (a>b)
                    return a;
                return b;
            }
            

            從下面可以看出代碼都差不多,唯一的區別是最后兩個iload指令(偏移位5和偏移位7)互換了。

            public static int max(int, int);
            flags: ACC_PUBLIC, ACC_STATIC
            Code:
            stack=2, locals=2, args_size=2
            0: iload_0
            1: iload_1
            2: if_icmple 7
            5: iload_0
            6: ireturn
            7: iload_1
            8: ireturn
            

            更復雜的例子。。

            #!java
            public class cond
            {
                public static void f(int i)
                {
                    if (i<100)
                        System.out.print("<100");
                    if (i==100)
                        System.out.print("==100");
                    if (i>100)
                        System.out.print(">100");
                    if (i==0)
                        System.out.print("==0");
                }
            }
            

            public static void f(int);
            flags: ACC_PUBLIC, ACC_STATIC
            Code:
            stack=2, locals=1, args_size=1
            0: iload_0
            1: bipush 100
            3: if_icmpge 14
            6: getstatic #2 // Field java/?
            ? lang/System.out:Ljava/io/PrintStream;
            9: ldc #3 // String <100
            11: invokevirtual #4 // Method java/io?
            ? /PrintStream.print:(Ljava/lang/String;)V
            14: iload_0
            15: bipush 100
            17: if_icmpne 28
            20: getstatic #2 // Field java/?
            ? lang/System.out:Ljava/io/PrintStream;
            23: ldc #5 // String ==100
            25: invokevirtual #4 // Method java/io?
            ? /PrintStream.print:(Ljava/lang/String;)V
            28: iload_0
            29: bipush 100
            31: if_icmple 42
            34: getstatic #2 // Field java/?
            ? lang/System.out:Ljava/io/PrintStream;
            37: ldc #6 // String >100
            39: invokevirtual #4 // Method java/io?
            ? /PrintStream.print:(Ljava/lang/String;)V
            42: iload_0
            43: ifne 54
            46: getstatic #2 // Field java/?
            ? lang/System.out:Ljava/io/PrintStream;
            49: ldc #7 // String ==0
            51: invokevirtual #4 // Method java/io?
            ? /PrintStream.print:(Ljava/lang/String;)V
            54: return
            

            if_icmpge出棧兩個值,并且比較兩個數值,如果第的二個值大于第一個,跳轉到偏移位14,if_icmpne和if_icmple做的工作類似,但是使用不同的判斷條件。

            在行偏移43的ifne指令,它的名字不是很恰當,我更愿意把它命名為ifnz(if not zero 可能是冷笑話)(如果棧定的值不是0則跳轉),當不是0的時候,跳轉到偏移54,如果輸入的值不是另,如果是0,執行流程進入偏移46,并且打印字符串“==0”。

            JVM沒有無符號數據類型,所以,只能通過符號整數值進行比較指令操作。

            54.9 傳遞參數值


            讓我們稍微擴展一下min()和max()這個例子。

            #!java
            public class minmax
            {
                public static int min (int a, int b)
                {
                    if (a>b)
                        return b;
                    return a;
                }
                public static int max (int a, int b)
                {
                    if (a>b)
                        return a;
                    return b;
                }
                public static void main(String[] args)
                {
                    int a=123, b=456;
                    int max_value=max(a, b);
                    int min_value=min(a, b);
                    System.out.println(min_value);
                    System.out.println(max_value);
                }
            }
            

            下面是main()函數的代碼。

            public static void main(java.lang.String[]);
            flags: ACC_PUBLIC, ACC_STATIC
            Code:
            stack=2, locals=5, args_size=1
            0: bipush 123
            2: istore_1
            3: sipush 456
            6: istore_2
            7: iload_1
            8: iload_2
            9: invokestatic #2 // Method max:(II?
            ? )I
            12: istore_3
            13: iload_1
            14: iload_2
            15: invokestatic #3 // Method min:(II?
            ? )I
            18: istore 4
            20: getstatic #4 // Field java/?
            ? lang/System.out:Ljava/io/PrintStream;
            23: iload 4
            25: invokevirtual #5 // Method java/io?
            ? /PrintStream.println:(I)V
            28: getstatic #4 // Field java/?
            ? lang/System.out:Ljava/io/PrintStream;
            31: iload_3
            32: invokevirtual #5 // Method java/io?
            ? /PrintStream.println:(I)V
            35: return
            

            棧中的參數被傳遞給其他函數,并且將返回值置于棧頂。

            54.10位。


            java中的位操作其實與其他的一些ISA(指令集架構)類似:

            #!java
            public static int set (int a, int b)
            {
                return a | 1<<b;
            }
            
            public static int clear (int a, int b)
            {
                return a & (~(1<<b));
            }
            

            public static int set(int, int);
              flags: ACC_PUBLIC, ACC_STATIC
              Code:
                stack=3, locals=2, args_size=2
                0: iload_0
                1: iconst_1
                2: iload_1
                3: ishl
                4: ior
                5: ireturn
            
            public static int clear(int, int);
              flags: ACC_PUBLIC, ACC_STATIC
              Code:
                stack=3, locals=2, args_size=2
                0: iload_0
                1: iconst_1
                2: iload_1
                3: ishl
                4: iconst_m1
                5: ixor
                6: iand
                7: ireturn
            

            iconst_m1加載-1入棧,這數其實就是16進制的0xFFFFFFFF,將0xFFFFFFFF作為XOR-ing指令執行的操作數。起到的效果就是把所有bits位反向,(A.6.2在1406頁)

            我將所有數據類型,擴展成64為長整型。

            #!java
            public static long lset (long a, int b)
            {
                return a | 1<<b;
            }
            public static long lclear (long a, int b)
            {
                return a & (~(1<<b));
            }
            

            public static long lset(long, int);
              flags: ACC_PUBLIC, ACC_STATIC
              Code:
                stack=4, locals=3, args_size=2
                0: lload_0
                1: iconst_1
                2: iload_2
                3: ishl
                4: i2l
                5: lor
                6: lreturn
            public static long lclear(long, int);
              flags: ACC_PUBLIC, ACC_STATIC
              Code:
                stack=4, locals=3, args_size=2
                0: lload_0
                1: iconst_1
                2: iload_2
                3: ishl
                4: iconst_m1
                5: ixor
                6: i2l
                7: land
                8: lreturn
            

            代碼是相同的,但是操作64位值的指令的前綴變成了L,并且第二個函數參數還是int類型,當32位需要升級為64位值時,會使用i21指令把整型擴展成64位長整型.

            54.11循環


            #!java
            public class Loop
            {
                public static void main(String[] args)
                {
                    for (int i = 1; i <= 10; i++)
                    {
                        System.out.println(i);
                    }
                }
            }
            

            public static void main(java.lang.String[]);
            flags: ACC_PUBLIC, ACC_STATIC
            Code:
            stack=2, locals=2, args_size=1
            0: iconst_1
            1: istore_1
            2: iload_1
            3: bipush 10
            5: if_icmpgt 21
            8: getstatic #2 // Field java/?
            ? lang/System.out:Ljava/io/PrintStream;
            11: iload_1
            12: invokevirtual #3 // Method java/io?
            ? /PrintStream.println:(I)V
            15: iinc 1, 1
            18: goto 2
            21: return
            

            icont_1將1推入棧頂,istore_1將其存入到局部數組變量的儲存位1。

            可以注意到沒有使用第0個儲存位,因為main()函數只有一個指向其的引用的參數(String數組),就位于第0號槽中。

            因此,本地變量i 總是在第1儲存位中。 在行偏移3和行偏移5的位置,指令將i和10進行比較。如果i大于10,執行流將進入偏移21,之后函數會結束,如果i小于或等于10,則調用println。我們可以看到i在偏移11進行了重新加載,用于調用println。

            多說一句,我們調用pringln打印數據類型是整型,我們看注釋,“i,v”,i的意思是整型,v的意思是返回void。

            當println函數結束時,i進入偏移15,通過指令iinc將參數槽1的值,數值1與本地變量相加。

            goto指令就是跳轉,它跳轉偏移2,就是循環體的開始地址.

            下面讓我們來處理更復雜的例子

            #!java
            public class Fibonacci
            {
                public static void main(String[] args)
                {
                    int limit = 20, f = 0, g = 1;
                    for (int i = 1; i <= limit; i++)
                    {
                        f = f + g;
                        g = f - g;
                        System.out.println(f);
                    } 
                }
            }
            

            #!bash
            ?public static void main(java.lang.String[]);
              flags: ACC_PUBLIC, ACC_STATIC
              Code:
                stack=2, locals=5, args_size=1
                   0: bipush        20
                   2: istore_1
                   3: iconst_0
                   4: istore_2
                   5: iconst_1
                   6: istore_3
                   7: iconst_1
                   8: istore        4
                  10: iload         4
                  12: iload_1
                  13: if_icmpgt     37
                  16: iload_2
                  17: iload_3
                  18: iadd
                  19: istore_2
                  20: iload_2
                  21: iload_3
                  22: isub
                  23: istore_3
                  24: getstatic     #2
            

            <span id="7ztzv"></span>
            <sub id="7ztzv"></sub>

            <span id="7ztzv"></span><form id="7ztzv"></form>

            <span id="7ztzv"></span>

                  <address id="7ztzv"></address>

                      亚洲欧美在线