├── .gitignore ├── LICENSE ├── README.md ├── build.xml ├── manifest.mf ├── nbproject ├── build-impl.xml ├── genfiles.properties ├── project.properties └── project.xml └── src └── quickvm ├── Methods.java ├── Ops.java ├── QuickVM.java ├── VMObject.java └── util ├── ReaderUtil.java └── StringUtil.java /.gitignore: -------------------------------------------------------------------------------- 1 | /dist 2 | /build 3 | nbproject/private 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, Alba Mendez and the quickvm authors 2 | 3 | Permission to use, copy, modify, and distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # quickvm 2 | 3 | quickvm is a little tool that interprets Smali disassembly, 4 | like a real Dalvik VM would. 5 | 6 | The main motivation to write this was that some obfuscators 7 | *encrypt string constants*, putting code at the static constructor 8 | that decrypts them and stores them into static fields. Here's an 9 | example of a disassembled class with an encrypted string: 10 | 11 | ~~~ smali 12 | ... 13 | 14 | # static fields 15 | .field private static final z:Ljava/lang/String; 16 | 17 | # direct methods 18 | .method static constructor ()V 19 | .locals 5 20 | 21 | const-string/jumbo v0, "\u0011\u00172x\u00031\u001c{f\u0001\u007f\u0008}{\u0012\u007f\u0019di\u000f3\u0019pd\u0003" 22 | invoke-virtual {v0}, Ljava/lang/String;->toCharArray()[C 23 | move-result-object v0 24 | array-length v1, v0 25 | const/4 v2, 0x0 26 | move v3, v2 27 | move v2, v1 28 | move-object v1, v0 29 | 30 | :goto_0 31 | if-gt v2, v3, :cond_0 32 | new-instance v0, Ljava/lang/String; 33 | invoke-direct {v0, v1}, Ljava/lang/String;->([C)V 34 | invoke-virtual {v0}, Ljava/lang/String;->intern()Ljava/lang/String; 35 | move-result-object v0 36 | sput-object v0, Lde/greenrobot/event/d;->z:Ljava/lang/String; 37 | return-void 38 | 39 | :cond_0 40 | aget-char v4, v1, v3 41 | rem-int/lit8 v0, v3, 0x5 42 | packed-switch v0, :pswitch_data_0 43 | const/16 v0, 0x66 44 | 45 | :goto_1 46 | xor-int/2addr v0, v4 47 | int-to-char v0, v0 48 | aput-char v0, v1, v3 49 | add-int/lit8 v0, v3, 0x1 50 | move v3, v0 51 | goto :goto_0 52 | 53 | :pswitch_0 54 | const/16 v0, 0x5f 55 | goto :goto_1 56 | 57 | :pswitch_1 58 | const/16 v0, 0x78 59 | goto :goto_1 60 | 61 | :pswitch_2 62 | const/16 v0, 0x12 63 | goto :goto_1 64 | 65 | :pswitch_3 66 | const/16 v0, 0x8 67 | goto :goto_1 68 | 69 | nop 70 | 71 | :pswitch_data_0 72 | .packed-switch 0x0 73 | :pswitch_0 74 | :pswitch_1 75 | :pswitch_2 76 | :pswitch_3 77 | .end packed-switch 78 | .end method 79 | 80 | ... 81 | ~~~ 82 | 83 | quickvm's will interpret statements in the smali file, simulating a VM 84 | executing the static constructor, and tell you the values of static fields 85 | as the code initializes them. For the above class, quickvm would report: 86 | 87 | ~~~ 88 | Trying file: de/greenrobot/event/d.smali 89 | Lde/greenrobot/event/d;->z:Ljava/lang/String; = "No pending post available" 90 | ~~~ 91 | 92 | A nice advantage of this method is that it doesn't depend on any obfuscator 93 | in particular. It will decrypt them as long as the decryption takes place 94 | from the static constructor (but see limitations below). 95 | 96 | quickvm can work with (and print) all primitive values (booleans, chars and 97 | integers), plus strings, arrays and some common containers like `ArrayList`. 98 | Booleans and chars are printed as integers. Example: 99 | 100 | ~~~ 101 | Lde/greenrobot/event/util/h;->b:Z = 1 102 | Lde/greenrobot/event/d;->z:Ljava/lang/String; = "No pending post available" 103 | Lde/greenrobot/event/m;->z:[Ljava/lang/String; = [ "PostThread", "BackgroundThread", "MainThread", "Async" ] 104 | Lde/greenrobot/event/util/h;->a:I = 0 105 | Lorg/whispersystems/O;->a:[B = [ 1, 59, 3 ] 106 | Lorg/whispersystems/bF;->a:I = 16777215 107 | ~~~ 108 | 109 | So, even when the constants are not encrypted, it's still handy to have them 110 | in readable form, instead of having to read the static constructor. 111 | 112 | 113 | ## Usage 114 | 115 | Build the code (running `ant jar` should suffice) and then change to the root 116 | of the smali disassembly (this root will usually contain `com/`, `org/`, et all). 117 | From there: 118 | 119 | find | grep '\.smali$' | java -jar ~/path/to/quickvm/dist/quickvm.jar . > constants.txt 120 | 121 | And watch quickvm try to load each of your classes, and probably fail on 122 | most of them. Constants will be saved to `constants.txt`. 123 | 124 | If quickvm fails on most classes, it's probably because of some 125 | instruction or API call that the obfuscator likes to use and is not implemented. 126 | It should be implemented in `Ops.java` and `Methods.java` respectively. 127 | 128 | If most classes complete without errors (quickvm just moves to the next one) 129 | then good news! Even if some classes still fail, decryption is usually performed 130 | right at the beginning of the static constructor, so by the time those classes 131 | fail (see limitations below), their constants have probably been decrypted 132 | and printed already. However, reviewing those classes manually is still a good idea... 133 | 134 | ### Big codebases 135 | 136 | When running this at big codebases (500+ classes), it's better to skip loading 137 | classes you don't care about, such as support library, google, etc. It's also better 138 | to log the error stream as well, so we can review the errors later. 139 | 140 | find com/provider1 | grep '\.smali$' | java -jar ~/path/to/quickvm/dist/quickvm.jar . > constants.txt 2> status.txt 141 | 142 | 143 | ## Limitations 144 | 145 | Although a useful tool, it's still very limited for a VM: 146 | 147 | - No notion of type inheritance, so type casts, method calls, etc. 148 | can not be implemented with the current design. 149 | 150 | - The VM does not have exceptions, and implementing them would require 151 | significant refactoring and such. 152 | 153 | - No STL, only a few methods implemented in `Methods.java`. If the Smali calls 154 | any other method of the STL, you'll have to implement it too. 155 | 156 | - Slow as fuck. Which is expected, knowing this "VM" runs off Smali code 157 | instead of actual bytecode, doesn't have a GC, isn't written with efficiency 158 | in mind, and was coded in a few hours. 159 | 160 | - A lot of opcodes are not implemented yet. 64-bit operations, floating 161 | arithmetic, branching... Again, I hacked this up for my needs rather than 162 | as a serious project. 163 | 164 | In a way, some of these limitations were actually *intended*, because the aim 165 | of this tool is to extract static constants. By not implementing I/O, threads, 166 | or other fancy features, we can be sure that constants extracted by quickvm 167 | do not depend on the environment in any way. This is the fundamental difference 168 | between using this tool and making a memory dump of the Dalvik VM while the app 169 | is loaded. 170 | 171 | Issues and improvements are welcome. Keep in mind this is just a tool, and it's 172 | especially crappy. It can (and will) miss interesting stuff, so take the results 173 | with a pinch of salt. It's a good idea to make sure to only run this at Smali 174 | code coming straight from the disassembler, and make sure the assembled code 175 | runs on a real Android VM without crashing. 176 | 177 | 178 | -------------------------------------------------------------------------------- /build.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Builds, tests, and runs the project QuickVM. 12 | 13 | 73 | 74 | -------------------------------------------------------------------------------- /manifest.mf: -------------------------------------------------------------------------------- 1 | Manifest-Version: 1.0 2 | X-COMMENT: Main-Class will be added automatically by build 3 | 4 | -------------------------------------------------------------------------------- /nbproject/build-impl.xml: -------------------------------------------------------------------------------- 1 | 2 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | Must set src.dir 227 | Must set test.src.dir 228 | Must set build.dir 229 | Must set dist.dir 230 | Must set build.classes.dir 231 | Must set dist.javadoc.dir 232 | Must set build.test.classes.dir 233 | Must set build.test.results.dir 234 | Must set build.classes.excludes 235 | Must set dist.jar 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | Must set javac.includes 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | No tests executed. 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 | 524 | 525 | 526 | 527 | 528 | 529 | 530 | 531 | 532 | 533 | 534 | 535 | 536 | 537 | 538 | 539 | 540 | 541 | 542 | 543 | 544 | 545 | 546 | 547 | 548 | 549 | 550 | 551 | 552 | 553 | 554 | 555 | 556 | 557 | 558 | 559 | 560 | 561 | 562 | 563 | 564 | 565 | 566 | 567 | 568 | 569 | 570 | 571 | 572 | 573 | 574 | 575 | 576 | 577 | 578 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | 588 | 589 | 590 | 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 | 600 | 601 | 602 | 603 | 604 | 605 | 606 | 607 | 608 | 609 | 610 | 611 | 612 | 613 | 614 | 615 | 616 | 617 | 618 | 619 | 620 | 621 | 622 | 623 | 624 | 625 | 626 | 627 | 628 | 629 | 630 | 631 | 632 | 633 | 634 | 635 | 636 | 637 | 638 | 639 | 640 | 641 | 642 | 643 | 644 | 645 | 646 | 647 | 648 | 649 | 650 | 651 | 652 | 653 | 654 | 655 | 656 | 657 | 658 | 659 | 660 | 661 | 662 | 663 | 664 | 665 | 666 | 667 | 668 | 669 | 670 | 673 | 674 | 675 | 676 | 677 | 678 | 679 | 680 | 681 | 682 | 683 | 684 | 685 | 686 | 687 | 688 | 689 | 690 | 691 | 692 | 693 | 694 | 695 | 696 | 697 | 698 | 699 | 700 | 701 | 702 | 703 | 704 | 705 | 706 | 707 | 708 | 709 | 710 | 711 | 712 | 713 | 714 | 715 | Must set JVM to use for profiling in profiler.info.jvm 716 | Must set profiler agent JVM arguments in profiler.info.jvmargs.agent 717 | 718 | 721 | 722 | 723 | 724 | 725 | 726 | 727 | 728 | 729 | 730 | 731 | 732 | 733 | 734 | 735 | 736 | 737 | 738 | 739 | 740 | 741 | 742 | 743 | 744 | 745 | 746 | 747 | 748 | 749 | 750 | 751 | 752 | 753 | 754 | 755 | 756 | 757 | 758 | 759 | 760 | 761 | 762 | 763 | 764 | 765 | 766 | 767 | 768 | 769 | 770 | 771 | 772 | 773 | 774 | 775 | 776 | 777 | 778 | 779 | 780 | 781 | 782 | 783 | 784 | 785 | 786 | 787 | 788 | 789 | 790 | 791 | 792 | 793 | 794 | 795 | 796 | 797 | 798 | 799 | 800 | 801 | 802 | 803 | 804 | 805 | 806 | 807 | 808 | 809 | 810 | 811 | 812 | 813 | 814 | 815 | 816 | 817 | 818 | 819 | 820 | 821 | 822 | 823 | 824 | 825 | 826 | 827 | 828 | 829 | 830 | 831 | 832 | 833 | 834 | 835 | 836 | 837 | 838 | 839 | 840 | 841 | 842 | 843 | 844 | 845 | 846 | 847 | 848 | 849 | 850 | 851 | 852 | 853 | 854 | 855 | 856 | 857 | 858 | 859 | 860 | 861 | 862 | 863 | 864 | 865 | 866 | 867 | 868 | 869 | 870 | 871 | 872 | 873 | 874 | 875 | 876 | 877 | 878 | 879 | 884 | 885 | 886 | 887 | 888 | 889 | 890 | 891 | 892 | 893 | 894 | 895 | 896 | 897 | 898 | 899 | 900 | 901 | 902 | 903 | 904 | 905 | 906 | 907 | 908 | 909 | 910 | 911 | 912 | 913 | 914 | 915 | 916 | 917 | 918 | 919 | 920 | 921 | 922 | 923 | 924 | 925 | 926 | 927 | 928 | 929 | 930 | 931 | 932 | 933 | 934 | 935 | 936 | 937 | 938 | 939 | 940 | 941 | 942 | 943 | 944 | Must select some files in the IDE or set javac.includes 945 | 946 | 947 | 948 | 949 | 950 | 951 | 952 | 953 | 958 | 959 | 960 | 961 | 962 | 963 | 964 | 965 | 966 | 967 | 968 | 969 | 970 | 971 | 972 | 973 | 974 | 975 | 976 | 977 | 978 | 979 | 980 | 981 | 982 | 983 | 984 | 985 | 986 | 987 | 988 | 989 | 990 | 991 | 992 | 993 | 994 | To run this application from the command line without Ant, try: 995 | 996 | java -jar "${dist.jar.resolved}" 997 | 998 | 999 | 1000 | 1001 | 1002 | 1003 | 1004 | 1005 | 1006 | 1007 | 1008 | 1009 | 1010 | 1011 | 1012 | 1013 | 1014 | 1015 | 1016 | 1017 | 1018 | 1019 | 1020 | 1021 | 1022 | 1023 | 1024 | 1025 | 1026 | 1027 | 1032 | 1033 | 1034 | 1035 | 1036 | 1037 | 1038 | 1039 | 1040 | 1041 | 1042 | 1043 | Must select one file in the IDE or set run.class 1044 | 1045 | 1046 | 1047 | Must select one file in the IDE or set run.class 1048 | 1049 | 1050 | 1055 | 1056 | 1057 | 1058 | 1059 | 1060 | 1061 | 1062 | 1063 | 1064 | 1065 | 1066 | 1067 | 1068 | 1069 | 1070 | 1071 | 1072 | 1073 | 1074 | Must select one file in the IDE or set debug.class 1075 | 1076 | 1077 | 1078 | 1079 | Must select one file in the IDE or set debug.class 1080 | 1081 | 1082 | 1083 | 1084 | Must set fix.includes 1085 | 1086 | 1087 | 1088 | 1089 | 1090 | 1091 | 1096 | 1099 | 1100 | This target only works when run from inside the NetBeans IDE. 1101 | 1102 | 1103 | 1104 | 1105 | 1106 | 1107 | 1108 | 1109 | Must select one file in the IDE or set profile.class 1110 | This target only works when run from inside the NetBeans IDE. 1111 | 1112 | 1113 | 1114 | 1115 | 1116 | 1117 | 1118 | 1119 | This target only works when run from inside the NetBeans IDE. 1120 | 1121 | 1122 | 1123 | 1124 | 1125 | 1126 | 1127 | 1128 | 1129 | 1130 | 1131 | 1132 | This target only works when run from inside the NetBeans IDE. 1133 | 1134 | 1135 | 1136 | 1137 | 1138 | 1139 | 1140 | 1141 | 1142 | 1143 | 1144 | 1145 | 1146 | 1147 | 1148 | 1149 | 1150 | 1151 | 1152 | 1153 | 1154 | 1157 | 1158 | 1159 | 1160 | 1161 | 1162 | 1163 | 1164 | 1165 | 1166 | 1167 | 1168 | 1169 | 1170 | Must select one file in the IDE or set run.class 1171 | 1172 | 1173 | 1174 | 1175 | 1176 | Must select some files in the IDE or set test.includes 1177 | 1178 | 1179 | 1180 | 1181 | Must select one file in the IDE or set run.class 1182 | 1183 | 1184 | 1185 | 1186 | Must select one file in the IDE or set applet.url 1187 | 1188 | 1189 | 1190 | 1195 | 1196 | 1197 | 1198 | 1199 | 1200 | 1201 | 1202 | 1203 | 1204 | 1205 | 1206 | 1207 | 1208 | 1209 | 1210 | 1211 | 1212 | 1213 | 1214 | 1215 | 1216 | 1217 | 1218 | 1219 | 1220 | 1221 | 1222 | 1223 | 1224 | 1225 | 1226 | 1227 | 1228 | 1229 | 1230 | 1231 | 1232 | 1233 | 1234 | 1239 | 1240 | 1241 | 1242 | 1243 | 1244 | 1245 | 1246 | 1247 | 1248 | 1249 | 1250 | 1251 | 1252 | 1253 | 1254 | 1255 | 1256 | 1257 | 1258 | 1259 | 1260 | 1261 | 1262 | 1263 | 1264 | 1265 | Must select some files in the IDE or set javac.includes 1266 | 1267 | 1268 | 1269 | 1270 | 1271 | 1272 | 1273 | 1274 | 1275 | 1276 | 1277 | 1282 | 1283 | 1284 | 1285 | 1286 | 1287 | 1288 | 1289 | Some tests failed; see details above. 1290 | 1291 | 1292 | 1293 | 1294 | 1295 | 1296 | 1297 | 1298 | Must select some files in the IDE or set test.includes 1299 | 1300 | 1301 | 1302 | Some tests failed; see details above. 1303 | 1304 | 1305 | 1306 | Must select some files in the IDE or set test.class 1307 | Must select some method in the IDE or set test.method 1308 | 1309 | 1310 | 1311 | Some tests failed; see details above. 1312 | 1313 | 1314 | 1319 | 1320 | Must select one file in the IDE or set test.class 1321 | 1322 | 1323 | 1324 | Must select one file in the IDE or set test.class 1325 | Must select some method in the IDE or set test.method 1326 | 1327 | 1328 | 1329 | 1330 | 1331 | 1332 | 1333 | 1334 | 1335 | 1336 | 1337 | 1342 | 1343 | Must select one file in the IDE or set applet.url 1344 | 1345 | 1346 | 1347 | 1348 | 1349 | 1350 | 1355 | 1356 | Must select one file in the IDE or set applet.url 1357 | 1358 | 1359 | 1360 | 1361 | 1362 | 1363 | 1364 | 1369 | 1370 | 1371 | 1372 | 1373 | 1374 | 1375 | 1376 | 1377 | 1378 | 1379 | 1380 | 1381 | 1382 | 1383 | 1384 | 1385 | 1386 | 1387 | 1388 | 1389 | 1390 | 1391 | 1392 | 1393 | 1394 | 1395 | 1396 | 1397 | 1398 | 1399 | 1400 | 1401 | 1402 | 1403 | 1404 | 1405 | 1406 | 1407 | 1408 | 1409 | 1410 | 1411 | 1412 | 1413 | 1414 | -------------------------------------------------------------------------------- /nbproject/genfiles.properties: -------------------------------------------------------------------------------- 1 | build.xml.data.CRC32=1dfc7c5e 2 | build.xml.script.CRC32=4f718b5d 3 | build.xml.stylesheet.CRC32=8064a381@1.75.2.48 4 | # This file is used by a NetBeans-based IDE to track changes in generated files such as build-impl.xml. 5 | # Do not edit this file. You may delete it but then the IDE will never regenerate such files for you. 6 | nbproject/build-impl.xml.data.CRC32=1dfc7c5e 7 | nbproject/build-impl.xml.script.CRC32=02f84e45 8 | nbproject/build-impl.xml.stylesheet.CRC32=876e7a8f@1.75.2.48 9 | -------------------------------------------------------------------------------- /nbproject/project.properties: -------------------------------------------------------------------------------- 1 | annotation.processing.enabled=true 2 | annotation.processing.enabled.in.editor=false 3 | annotation.processing.processors.list= 4 | annotation.processing.run.all.processors=true 5 | annotation.processing.source.output=${build.generated.sources.dir}/ap-source-output 6 | application.title=QuickVM 7 | application.vendor=xavier 8 | build.classes.dir=${build.dir}/classes 9 | build.classes.excludes=**/*.java,**/*.form 10 | # This directory is removed when the project is cleaned: 11 | build.dir=build 12 | build.generated.dir=${build.dir}/generated 13 | build.generated.sources.dir=${build.dir}/generated-sources 14 | # Only compile against the classpath explicitly listed here: 15 | build.sysclasspath=ignore 16 | build.test.classes.dir=${build.dir}/test/classes 17 | build.test.results.dir=${build.dir}/test/results 18 | # Uncomment to specify the preferred debugger connection transport: 19 | #debug.transport=dt_socket 20 | debug.classpath=\ 21 | ${run.classpath} 22 | debug.test.classpath=\ 23 | ${run.test.classpath} 24 | # Files in build.classes.dir which should be excluded from distribution jar 25 | dist.archive.excludes= 26 | # This directory is removed when the project is cleaned: 27 | dist.dir=dist 28 | dist.jar=${dist.dir}/QuickVM.jar 29 | dist.javadoc.dir=${dist.dir}/javadoc 30 | endorsed.classpath= 31 | excludes= 32 | includes=** 33 | jar.compress=false 34 | javac.classpath= 35 | # Space-separated list of extra javac options 36 | javac.compilerargs= 37 | javac.deprecation=false 38 | javac.processorpath=\ 39 | ${javac.classpath} 40 | javac.source=1.7 41 | javac.target=1.7 42 | javac.test.classpath=\ 43 | ${javac.classpath}:\ 44 | ${build.classes.dir} 45 | javac.test.processorpath=\ 46 | ${javac.test.classpath} 47 | javadoc.additionalparam= 48 | javadoc.author=false 49 | javadoc.encoding=${source.encoding} 50 | javadoc.noindex=false 51 | javadoc.nonavbar=false 52 | javadoc.notree=false 53 | javadoc.private=false 54 | javadoc.splitindex=true 55 | javadoc.use=true 56 | javadoc.version=false 57 | javadoc.windowtitle= 58 | main.class=quickvm.QuickVM 59 | manifest.file=manifest.mf 60 | meta.inf.dir=${src.dir}/META-INF 61 | mkdist.disabled=false 62 | platform.active=default_platform 63 | run.classpath=\ 64 | ${javac.classpath}:\ 65 | ${build.classes.dir} 66 | # Space-separated list of JVM arguments used when running the project. 67 | # You may also define separate properties like run-sys-prop.name=value instead of -Dname=value. 68 | # To set system properties for unit tests define test-sys-prop.name=value: 69 | run.jvmargs= 70 | run.test.classpath=\ 71 | ${javac.test.classpath}:\ 72 | ${build.test.classes.dir} 73 | source.encoding=UTF-8 74 | src.dir=src 75 | test.src.dir=test 76 | -------------------------------------------------------------------------------- /nbproject/project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | org.netbeans.modules.java.j2seproject 4 | 5 | 6 | QuickVM 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/quickvm/Methods.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this license header, choose License Headers in Project Properties. 3 | * To change this template file, choose Tools | Templates 4 | * and open the template in the editor. 5 | */ 6 | package quickvm; 7 | 8 | import java.io.UnsupportedEncodingException; 9 | import java.util.ArrayList; 10 | import java.util.HashMap; 11 | import java.util.HashSet; 12 | import java.util.Map; 13 | 14 | /** 15 | * 16 | * @author xavier 17 | */ 18 | public class Methods { 19 | public static void addAll(Map handlers) { 20 | 21 | handlers.put("Ljava/lang/Object;->()V", new QuickVM.MethodHandler() { 22 | @Override 23 | public void handle(QuickVM vm, int[] params) { 24 | } 25 | }); 26 | 27 | handlers.put("[->clone()Ljava/lang/Object;", new QuickVM.MethodHandler() { 28 | @Override 29 | public void handle(QuickVM vm, int[] params) { 30 | VMObject.ArrayObject arr = (VMObject.ArrayObject) vm.heap.get(params[0]); 31 | vm.returnValue = vm.allocateObject(new VMObject.ArrayObject(vm, arr.type, arr.value.clone())); 32 | } 33 | }); 34 | 35 | /** java.lang.String **/ 36 | 37 | handlers.put("Ljava/lang/String;", new QuickVM.MethodHandler() { 38 | @Override 39 | public void handle(QuickVM vm, int[] params) { 40 | vm.returnValue = vm.allocateObject(new VMObject.StringObject(null)); 41 | } 42 | }); 43 | 44 | handlers.put("Ljava/lang/String;->([C)V", new QuickVM.MethodHandler() { 45 | @Override 46 | public void handle(QuickVM vm, int[] params) { 47 | int[] locals = vm.stack.peek().locals; 48 | VMObject.StringObject str = (VMObject.StringObject) vm.heap.get(params[0]); 49 | VMObject.ArrayObject arr = (VMObject.ArrayObject) vm.heap.get(params[1]); 50 | char[] characters = new char[arr.value.length]; 51 | for (int i = 0; i < characters.length; i++) 52 | characters[i] = (char) arr.value[i]; 53 | str.value = new String(characters); 54 | } 55 | }); 56 | 57 | handlers.put("Ljava/lang/String;->intern()Ljava/lang/String;", new QuickVM.MethodHandler() { 58 | @Override 59 | public void handle(QuickVM vm, int[] params) { 60 | // FIXME: yeah, right... we don't have a GC and you're asking me to recycle references... 61 | vm.returnValue = params[0]; 62 | } 63 | }); 64 | 65 | handlers.put("Ljava/lang/String;->charAt(I)C", new QuickVM.MethodHandler() { 66 | @Override 67 | public void handle(QuickVM vm, int[] params) { 68 | VMObject.StringObject str = (VMObject.StringObject) vm.heap.get(params[0]); 69 | vm.returnValue = str.value.charAt(params[1]); 70 | } 71 | }); 72 | 73 | handlers.put("Ljava/lang/String;->length()I", new QuickVM.MethodHandler() { 74 | @Override 75 | public void handle(QuickVM vm, int[] params) { 76 | VMObject.StringObject str = (VMObject.StringObject) vm.heap.get(params[0]); 77 | vm.returnValue = str.value.length(); 78 | } 79 | }); 80 | 81 | handlers.put("Ljava/lang/String;->toCharArray()[C", new QuickVM.MethodHandler() { 82 | @Override 83 | public void handle(QuickVM vm, int[] params) { 84 | int[] locals = vm.stack.peek().locals; 85 | VMObject.StringObject str = (VMObject.StringObject) vm.heap.get(params[0]); 86 | int[] characters = new int[str.value.length()]; 87 | for (int i = 0; i < characters.length; i++) 88 | characters[i] = str.value.charAt(i); //FIXME: probably not what we want if char is negative, we shouldn't extend sign 89 | vm.returnValue = vm.allocateObject(new VMObject.ArrayObject(vm, "[C", characters)); 90 | } 91 | }); 92 | 93 | handlers.put("Ljava/lang/String;->getBytes()[B", new QuickVM.MethodHandler() { 94 | @Override 95 | public void handle(QuickVM vm, int[] params) { 96 | int[] locals = vm.stack.peek().locals; 97 | VMObject.StringObject str = (VMObject.StringObject) vm.heap.get(params[0]); 98 | byte[] bytes = str.value.getBytes(); 99 | int[] result = new int[bytes.length]; 100 | for (int i = 0; i < result.length; i++) 101 | result[i] = bytes[i]; //FIXME: probably not what we want if char is negative, we shouldn't extend sign 102 | vm.returnValue = vm.allocateObject(new VMObject.ArrayObject(vm, "[B", result)); 103 | } 104 | }); 105 | 106 | handlers.put("Ljava/lang/String;->getBytes(Ljava/lang/String;)[B", new QuickVM.MethodHandler() { 107 | @Override 108 | public void handle(QuickVM vm, int[] params) { 109 | int[] locals = vm.stack.peek().locals; 110 | VMObject.StringObject str = (VMObject.StringObject) vm.heap.get(params[0]); 111 | VMObject.StringObject arg = (VMObject.StringObject) vm.heap.get(params[1]); 112 | byte[] bytes; 113 | try { 114 | bytes = str.value.getBytes(arg.value); 115 | } catch (UnsupportedEncodingException ex) { 116 | throw new IllegalStateException("Unsupported enconding requested: " + arg.value); 117 | } 118 | int[] result = new int[bytes.length]; 119 | for (int i = 0; i < result.length; i++) 120 | result[i] = bytes[i]; //FIXME: probably not what we want if char is negative, we shouldn't extend sign 121 | vm.returnValue = vm.allocateObject(new VMObject.ArrayObject(vm, "[B", result)); 122 | } 123 | }); 124 | 125 | /** java.lang.StringBuilder **/ 126 | 127 | handlers.put("Ljava/lang/StringBuilder;", new QuickVM.MethodHandler() { 128 | @Override 129 | public void handle(QuickVM vm, int[] params) { 130 | vm.returnValue = vm.allocateObject(new VMObject.StringBuilderObject()); 131 | } 132 | }); 133 | 134 | handlers.put("Ljava/lang/StringBuilder;->()V", new QuickVM.MethodHandler() { 135 | @Override 136 | public void handle(QuickVM vm, int[] params) { 137 | } 138 | }); 139 | 140 | handlers.put("Ljava/lang/StringBuilder;->(Ljava/lang/String;)V", new QuickVM.MethodHandler() { 141 | @Override 142 | public void handle(QuickVM vm, int[] params) { 143 | VMObject.StringBuilderObject sbObj = (VMObject.StringBuilderObject) vm.heap.get(params[0]); 144 | VMObject.StringObject str = (VMObject.StringObject) vm.heap.get(params[1]); 145 | sbObj.value.append(str.value); 146 | } 147 | }); 148 | 149 | handlers.put("Ljava/lang/StringBuilder;->append(C)Ljava/lang/StringBuilder;", new QuickVM.MethodHandler() { 150 | @Override 151 | public void handle(QuickVM vm, int[] params) { 152 | VMObject.StringBuilderObject sbObj = (VMObject.StringBuilderObject) vm.heap.get(params[0]); 153 | sbObj.value.append((char) params[1]); 154 | vm.returnValue = params[0]; 155 | } 156 | }); 157 | 158 | handlers.put("Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;", new QuickVM.MethodHandler() { 159 | @Override 160 | public void handle(QuickVM vm, int[] params) { 161 | VMObject.StringBuilderObject sbObj = (VMObject.StringBuilderObject) vm.heap.get(params[0]); 162 | VMObject.StringObject str = (VMObject.StringObject) vm.heap.get(params[1]); 163 | sbObj.value.append(str.value); 164 | vm.returnValue = params[0]; 165 | } 166 | }); 167 | 168 | handlers.put("Ljava/lang/StringBuilder;->append(I)Ljava/lang/StringBuilder;", new QuickVM.MethodHandler() { 169 | @Override 170 | public void handle(QuickVM vm, int[] params) { 171 | VMObject.StringBuilderObject sbObj = (VMObject.StringBuilderObject) vm.heap.get(params[0]); 172 | sbObj.value.append(params[1]); 173 | vm.returnValue = params[0]; 174 | } 175 | }); 176 | 177 | handlers.put("Ljava/lang/StringBuilder;->toString()Ljava/lang/String;", new QuickVM.MethodHandler() { 178 | @Override 179 | public void handle(QuickVM vm, int[] params) { 180 | VMObject.StringBuilderObject sbObj = (VMObject.StringBuilderObject) vm.heap.get(params[0]); 181 | vm.returnValue = vm.allocateObject(new VMObject.StringObject(sbObj.value.toString())); 182 | } 183 | }); 184 | 185 | /** java.lang.Enum **/ 186 | 187 | handlers.put("Ljava/lang/Enum;->(Ljava/lang/String;I)V", new QuickVM.MethodHandler() { 188 | @Override 189 | public void handle(QuickVM vm, int[] params) { 190 | //FIXME: not interesting at the moment 191 | } 192 | }); 193 | 194 | /** java.util.ArrayList **/ 195 | 196 | handlers.put("Ljava/util/ArrayList;", new QuickVM.MethodHandler() { 197 | @Override 198 | public void handle(QuickVM vm, int[] params) { 199 | vm.returnValue = vm.allocateObject(new VMObject.ArrayListObject(vm)); 200 | } 201 | }); 202 | 203 | handlers.put("Ljava/util/ArrayList;->()V", new QuickVM.MethodHandler() { 204 | @Override 205 | public void handle(QuickVM vm, int[] params) { 206 | VMObject.ArrayListObject alObj = (VMObject.ArrayListObject) vm.heap.get(params[0]); 207 | alObj.value = new ArrayList<>(); 208 | } 209 | }); 210 | 211 | handlers.put("Ljava/util/ArrayList;->add(Ljava/lang/Object;)V", new QuickVM.MethodHandler() { 212 | @Override 213 | public void handle(QuickVM vm, int[] params) { 214 | VMObject.ArrayListObject alObj = (VMObject.ArrayListObject) vm.heap.get(params[0]); 215 | alObj.value.add(params[1]); 216 | } 217 | }); 218 | 219 | handlers.put("Ljava/util/ArrayList;->get(I)Ljava/lang/Object;", new QuickVM.MethodHandler() { 220 | @Override 221 | public void handle(QuickVM vm, int[] params) { 222 | VMObject.ArrayListObject alObj = (VMObject.ArrayListObject) vm.heap.get(params[0]); 223 | vm.returnValue = alObj.value.get(params[1]); 224 | } 225 | }); 226 | 227 | handlers.put("Ljava/util/Arrays;->asList([Ljava/lang/Object;)Ljava/util/List;", new QuickVM.MethodHandler() { 228 | @Override 229 | public void handle(QuickVM vm, int[] params) { 230 | VMObject.ArrayObject arr = (VMObject.ArrayObject) vm.heap.get(params[0]); 231 | VMObject.ArrayListObject alObj = new VMObject.ArrayListObject(vm); 232 | alObj.value = new ArrayList<>(arr.value.length); 233 | for (int o : arr.value) alObj.value.add(o); 234 | vm.returnValue = vm.allocateObject(alObj); 235 | } 236 | }); 237 | 238 | handlers.put("Ljava/util/Collections;->emptyList()Ljava/util/List;", new QuickVM.MethodHandler() { 239 | @Override 240 | public void handle(QuickVM vm, int[] params) { 241 | VMObject.ArrayListObject obj = new VMObject.ArrayListObject(vm); 242 | obj.value = new ArrayList<>(); 243 | vm.returnValue = vm.allocateObject(obj); 244 | } 245 | }); 246 | 247 | /** java.util.HashMap **/ 248 | 249 | handlers.put("Ljava/util/HashMap;", new QuickVM.MethodHandler() { 250 | @Override 251 | public void handle(QuickVM vm, int[] params) { 252 | vm.returnValue = vm.allocateObject(new VMObject.HashMapObject(vm)); 253 | } 254 | }); 255 | 256 | handlers.put("Ljava/util/HashMap;->()V", new QuickVM.MethodHandler() { 257 | @Override 258 | public void handle(QuickVM vm, int[] params) { 259 | VMObject.HashMapObject hObj = (VMObject.HashMapObject) vm.heap.get(params[0]); 260 | hObj.value = new HashMap<>(); 261 | } 262 | }); 263 | 264 | /*handlers.put("Ljava/util/HashMap;->get(Ljava/lang/Object;)Ljava/lang/Object;", new QuickVM.MethodHandler() { 265 | @Override 266 | public void handle(QuickVM vm, int[] params) { 267 | VMObject.HashMapObject hObj = (VMObject.HashMapObject) vm.heap.get(params[0]); 268 | vm.returnValue = hObj.value.get(params[1]); 269 | } 270 | });*/ 271 | 272 | handlers.put("Ljava/util/HashMap;->put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", new QuickVM.MethodHandler() { 273 | @Override 274 | public void handle(QuickVM vm, int[] params) { 275 | VMObject.HashMapObject hObj = (VMObject.HashMapObject) vm.heap.get(params[0]); 276 | vm.returnValue = hObj.value.put(params[1], params[2]); 277 | } 278 | }); 279 | 280 | handlers.put("Ljava/util/Collections;->emptyMap()Ljava/util/Map;", new QuickVM.MethodHandler() { 281 | @Override 282 | public void handle(QuickVM vm, int[] params) { 283 | VMObject.HashMapObject obj = new VMObject.HashMapObject(vm); 284 | obj.value = new HashMap<>(); 285 | vm.returnValue = vm.allocateObject(obj); 286 | } 287 | }); 288 | 289 | /** java.util.HashSet **/ 290 | 291 | handlers.put("Ljava/util/HashSet;", new QuickVM.MethodHandler() { 292 | @Override 293 | public void handle(QuickVM vm, int[] params) { 294 | vm.returnValue = vm.allocateObject(new VMObject.HashSetObject(vm)); 295 | } 296 | }); 297 | 298 | handlers.put("Ljava/util/HashSet;->()V", new QuickVM.MethodHandler() { 299 | @Override 300 | public void handle(QuickVM vm, int[] params) { 301 | VMObject.HashSetObject hObj = (VMObject.HashSetObject) vm.heap.get(params[0]); 302 | hObj.value = new HashSet<>(); 303 | } 304 | }); 305 | 306 | handlers.put("Ljava/util/HashSet;->(Ljava/util/Collection;)V", new QuickVM.MethodHandler() { 307 | @Override 308 | public void handle(QuickVM vm, int[] params) { 309 | VMObject.HashSetObject hObj = (VMObject.HashSetObject) vm.heap.get(params[0]); 310 | VMObject.ArrayListObject arr = (VMObject.ArrayListObject) vm.heap.get(params[1]); 311 | hObj.value = new HashSet<>(arr.value); 312 | } 313 | }); 314 | 315 | handlers.put("Ljava/util/HashSet;->add(Ljava/lang/Object;)Z", new QuickVM.MethodHandler() { 316 | @Override 317 | public void handle(QuickVM vm, int[] params) { 318 | VMObject.HashSetObject hObj = (VMObject.HashSetObject) vm.heap.get(params[0]); 319 | vm.returnValue = hObj.value.add(params[1]) ? 1 : 0; 320 | } 321 | }); 322 | 323 | /** TODO: 324 | LinkedHashMap 325 | Hashtable 326 | */ 327 | 328 | } 329 | } 330 | -------------------------------------------------------------------------------- /src/quickvm/Ops.java: -------------------------------------------------------------------------------- 1 | package quickvm; 2 | 3 | import java.io.IOException; 4 | import java.io.Reader; 5 | import java.util.Map; 6 | import quickvm.QuickVM.Frame; 7 | import quickvm.QuickVM.OpHandler; 8 | import quickvm.util.StringUtil; 9 | import quickvm.util.ReaderUtil; 10 | import static quickvm.util.StringUtil.stripComment; 11 | 12 | /** 13 | * Implements the virtual machine operations. 14 | */ 15 | public class Ops { 16 | 17 | public static void addAll(Map handlers) { 18 | 19 | /** ATTRIBUTES **/ 20 | 21 | handlers.put(".catch", new OpHandler() { 22 | @Override 23 | public void handle(QuickVM vm, String line) throws IOException { 24 | //FIXME: we don't have exceptions for now. just assume that if everything went okay 25 | //there's no way we can hit a catch 26 | } 27 | }); 28 | 29 | handlers.put(".end", new OpHandler() { 30 | @Override 31 | public void handle(QuickVM vm, String line) throws IOException { 32 | throw new IllegalArgumentException("Hit end of method, wtf"); 33 | } 34 | }); 35 | 36 | handlers.put(".line", new OpHandler() { 37 | @Override 38 | public void handle(QuickVM vm, String line) throws IOException { 39 | //FIXME: add info to frame, or something 40 | } 41 | }); 42 | 43 | handlers.put(".prologue", new OpHandler() { 44 | @Override 45 | public void handle(QuickVM vm, String line) throws IOException { 46 | //FIXME: add info to frame, or something 47 | } 48 | }); 49 | 50 | /** CONSTANTS **/ 51 | 52 | handlers.put("const", new OpHandler() { 53 | @Override 54 | public void handle(QuickVM vm, String line) throws IOException { 55 | int[] locals = vm.stack.peek().locals; 56 | String[] args = line.split("\\s+", 2)[1].split(",", 2); 57 | int dst = parseRegister(vm, args[0].trim()); 58 | long c = parseIntegerLiteral(args[1].trim()); 59 | 60 | locals[dst] = (int) c;//FIXME: overflowing casts defined in java? 61 | } 62 | }); 63 | 64 | handlers.put("const-string", new OpHandler() { 65 | @Override 66 | public void handle(QuickVM vm, String line) throws IOException { 67 | int[] locals = vm.stack.peek().locals; 68 | String[] args = line.split("\\s+", 2)[1].split(",", 2); 69 | int dst = parseRegister(vm, args[0].trim()); 70 | String str = parseStringLiteral(args[1].trim()); 71 | 72 | int obj = vm.allocateObject(new VMObject.StringObject(str)); 73 | locals[dst] = obj; 74 | } 75 | }); 76 | 77 | handlers.put("fill-array-data", new OpHandler() { 78 | @Override 79 | public void handle(QuickVM vm, String line_) throws IOException { 80 | int[] locals = vm.stack.peek().locals; 81 | String[] args = line_.split("\\s+", 2)[1].split(",", 2); 82 | int arr = parseRegister(vm, args[0].trim()); 83 | String label = args[1].trim(); 84 | if (!label.startsWith(":")) throw new IllegalArgumentException("Malformed syntax"); 85 | 86 | // FIXME: we need to obtain a reader to use temporally, so we 87 | // open a scope to the same method and then pop it off the stack. 88 | // This is extremely hacky, I know. 89 | vm.openScope(vm.stack.peek().name, new int[0]); 90 | try (Reader r = vm.stack.peek().source) { 91 | vm.jumpToLabel(label.substring(1)); 92 | vm.stack.pop(); 93 | 94 | // Read the array data, filling the array as we go 95 | line_ = ReaderUtil.readLine(r).trim(); 96 | String[] line = line_.split("\\s+", 2); 97 | if (!line[0].equals(".array-data")) 98 | throw new IllegalArgumentException("Malformed syntax, expected array data: " + line_); 99 | VMObject.ArrayObject array = (VMObject.ArrayObject) vm.heap.get(locals[arr]); 100 | 101 | int offset = 0; 102 | while (true) { 103 | line_ = stripComment(ReaderUtil.readLine(r)).trim(); 104 | if (line_.split("\\s+")[0].trim().startsWith(".end")) break; 105 | if (!line_.endsWith("t")) 106 | throw new IllegalArgumentException("Malformed syntax, expected array data: " + line_); 107 | array.value[offset++] = (int) parseIntegerLiteral(line_.substring(0, line_.length()-1)); 108 | } 109 | } 110 | } 111 | }); 112 | 113 | /** BRANCHING **/ 114 | 115 | handlers.put("return-void", new OpHandler() { 116 | @Override 117 | public void handle(QuickVM vm, String line) throws IOException { 118 | vm.closeScope(); 119 | } 120 | }); 121 | 122 | handlers.put("return-object", new OpHandler() { 123 | @Override 124 | public void handle(QuickVM vm, String line) throws IOException { 125 | int[] locals = vm.stack.peek().locals; 126 | String[] args = line.split("\\s+", 2)[1].split(",", 1); 127 | int val = parseRegister(vm, args[0].trim()); 128 | 129 | vm.returnValue = locals[val]; 130 | vm.closeScope(); 131 | } 132 | }); 133 | 134 | handlers.put("if-gt", new OpHandler() { 135 | @Override 136 | public void handle(QuickVM vm, String line) throws IOException { 137 | int[] locals = vm.stack.peek().locals; 138 | String[] args = line.split("\\s+", 2)[1].split(",", 3); 139 | int o1 = parseRegister(vm, args[0].trim()); 140 | int o2 = parseRegister(vm, args[1].trim()); 141 | String label = args[2].trim(); 142 | if (!label.startsWith(":")) throw new IllegalArgumentException("Malformed syntax"); 143 | if (locals[o1] > locals[o2]) vm.jumpToLabel(label.substring(1)); 144 | } 145 | }); 146 | 147 | handlers.put("if-ge", new OpHandler() { 148 | @Override 149 | public void handle(QuickVM vm, String line) throws IOException { 150 | int[] locals = vm.stack.peek().locals; 151 | String[] args = line.split("\\s+", 2)[1].split(",", 3); 152 | int o1 = parseRegister(vm, args[0].trim()); 153 | int o2 = parseRegister(vm, args[1].trim()); 154 | String label = args[2].trim(); 155 | if (!label.startsWith(":")) throw new IllegalArgumentException("Malformed syntax"); 156 | if (locals[o1] >= locals[o2]) vm.jumpToLabel(label.substring(1)); 157 | } 158 | }); 159 | 160 | handlers.put("goto", new OpHandler() { 161 | @Override 162 | public void handle(QuickVM vm, String line) throws IOException { 163 | int[] locals = vm.stack.peek().locals; 164 | String[] args = line.split("\\s+", 2)[1].split(",", 1); 165 | String label = args[0]; 166 | if (!label.startsWith(":")) throw new IllegalArgumentException("Malformed syntax"); 167 | vm.jumpToLabel(label.substring(1)); 168 | } 169 | }); 170 | 171 | handlers.put("return-void", new OpHandler() { 172 | @Override 173 | public void handle(QuickVM vm, String line) throws IOException { 174 | vm.closeScope(); 175 | } 176 | }); 177 | 178 | handlers.put("packed-switch", new OpHandler() { 179 | @Override 180 | public void handle(QuickVM vm, String line_) throws IOException { 181 | int[] locals = vm.stack.peek().locals; 182 | String[] args = line_.split("\\s+", 2)[1].split(",", 2); 183 | int val = parseRegister(vm, args[0].trim()); 184 | String label = args[1].trim(); 185 | if (!label.startsWith(":")) throw new IllegalArgumentException("Malformed syntax"); 186 | 187 | // FIXME: we need to obtain a reader to use temporally, so we 188 | // open a scope to the same method and then pop it off the stack. 189 | // This is extremely hacky, I know. 190 | vm.openScope(vm.stack.peek().name, new int[0]); 191 | try (Reader r = vm.stack.peek().source) { 192 | vm.jumpToLabel(label.substring(1)); 193 | vm.stack.pop(); 194 | 195 | // Read the packed switch definition, jumping where appropiate 196 | line_ = ReaderUtil.readLine(r).trim(); 197 | String[] line = line_.split("\\s+", 2); 198 | if (!line[0].equals(".packed-switch")) 199 | throw new IllegalArgumentException("Malformed syntax, expected packed switch: " + line_); 200 | int offset = (int) parseIntegerLiteral(line[1]); //FIXME: overflowing casts defined in java? 201 | int value = locals[val]; //FIXME: is this what we want? int? probably not, should always work with longs 202 | 203 | if (value < offset) return; 204 | while (true) { 205 | line_ = ReaderUtil.readLine(r).trim(); 206 | if (line_.split("\\s+")[0].trim().startsWith(".end")) break; 207 | if (!line_.startsWith(":")) throw new IllegalArgumentException("Malformed syntax, expected label: "+line_); 208 | if (value == offset) { 209 | vm.jumpToLabel(line_.substring(1)); 210 | return; 211 | } 212 | offset++; 213 | } 214 | } 215 | } 216 | }); 217 | 218 | /** REGISTER ACCESS **/ 219 | 220 | handlers.put("move", new OpHandler() { 221 | @Override 222 | public void handle(QuickVM vm, String line) throws IOException { 223 | int[] locals = vm.stack.peek().locals; 224 | String[] args = line.split("\\s+", 2)[1].split(",", 2); 225 | int dst = parseRegister(vm, args[0].trim()); 226 | int src = parseRegister(vm, args[1].trim()); 227 | 228 | locals[dst] = locals[src]; 229 | } 230 | }); 231 | 232 | handlers.put("move-object", handlers.get("move")); 233 | 234 | handlers.put("move-result", new OpHandler() { 235 | @Override 236 | public void handle(QuickVM vm, String line) throws IOException { 237 | int[] locals = vm.stack.peek().locals; 238 | String[] args = line.split("\\s+", 2)[1].split(",", 1); 239 | int dst = parseRegister(vm, args[0].trim()); 240 | 241 | locals[dst] = vm.returnValue; 242 | } 243 | }); 244 | 245 | handlers.put("move-result-object", handlers.get("move-result")); 246 | 247 | /** METHOD INVOCATION, INSTANCE CREATION **/ 248 | 249 | handlers.put("new-instance", new OpHandler() { 250 | @Override 251 | public void handle(QuickVM vm, String line) throws IOException { 252 | int[] locals = vm.stack.peek().locals; 253 | String[] args = line.split("\\s+", 2)[1].split(",", 2); 254 | int dst = parseRegister(vm, args[0].trim()); 255 | String type = args[1].trim(); 256 | 257 | if (vm.methods.containsKey(type)) { 258 | vm.methods.get(type).handle(vm, new int[0]); 259 | locals[dst] = vm.returnValue; 260 | } else { 261 | int obj = vm.allocateObject(new VMObject.VirtualObject(vm, type)); 262 | locals[dst] = obj; 263 | } 264 | } 265 | }); 266 | 267 | handlers.put("new-array", new OpHandler() { 268 | @Override 269 | public void handle(QuickVM vm, String line) throws IOException { 270 | int[] locals = vm.stack.peek().locals; 271 | String[] args = line.split("\\s+", 2)[1].split(",", 3); 272 | int dst = parseRegister(vm, args[0].trim()); 273 | int size = parseRegister(vm, args[1].trim()); 274 | String type = args[2].trim(); 275 | 276 | locals[dst] = vm.allocateObject(new VMObject.ArrayObject(vm, type, new int[locals[size]])); 277 | } 278 | }); 279 | 280 | handlers.put("invoke-direct", new OpHandler() { 281 | @Override 282 | public void handle(QuickVM vm, String line) throws IOException { 283 | int[] locals = vm.stack.peek().locals; 284 | String args = line.split("\\s+", 2)[1]; 285 | if (!args.startsWith("{") || !args.contains("}")) throw new IllegalArgumentException("Malformed syntax: "+line); 286 | String params = args.substring(1, args.indexOf("}")); 287 | String name = args.substring(params.length()+2).split(",", 2)[1].trim(); 288 | 289 | int[] regs; 290 | if (params.trim().isEmpty()) { 291 | regs = new int[0]; 292 | } else { 293 | String[] regsStr = params.split(","); 294 | regs = new int[regsStr.length]; 295 | for (int i = 0; i < regs.length; i++) 296 | regs[i] = locals[parseRegister(vm, regsStr[i].trim())]; 297 | } 298 | 299 | vm.openScope(name, regs); 300 | } 301 | }); 302 | 303 | handlers.put("invoke-static", handlers.get("invoke-direct")); 304 | 305 | //TODO: SOLVE THIS!! 306 | handlers.put("invoke-virtual", handlers.get("invoke-direct")); 307 | 308 | handlers.put("filled-new-array", new OpHandler() { 309 | @Override 310 | public void handle(QuickVM vm, String line) throws IOException { 311 | int[] locals = vm.stack.peek().locals; 312 | String args = line.split("\\s+", 2)[1]; 313 | if (!args.startsWith("{") || !args.contains("}")) throw new IllegalArgumentException("Malformed syntax: "+line); 314 | String params = args.substring(1, args.indexOf("}")); 315 | String type = args.substring(params.length()+2).split(",", 2)[1].trim(); 316 | 317 | int[] regs; 318 | if (params.trim().isEmpty()) { 319 | regs = new int[0]; 320 | } else { 321 | String[] regsStr = params.split(","); 322 | regs = new int[regsStr.length]; 323 | for (int i = 0; i < regs.length; i++) 324 | regs[i] = locals[parseRegister(vm, regsStr[i].trim())]; 325 | } 326 | 327 | vm.returnValue = vm.allocateObject(new VMObject.ArrayObject(vm, type, regs)); 328 | } 329 | }); 330 | 331 | /** FIELD & ARRAY MANIPULATION **/ 332 | 333 | handlers.put("iget", new OpHandler() { 334 | @Override 335 | public void handle(QuickVM vm, String line) throws IOException { 336 | int[] locals = vm.stack.peek().locals; 337 | String[] args = line.split("\\s+", 2)[1].split(",", 3); 338 | int val = parseRegister(vm, args[0].trim()); 339 | int obj = parseRegister(vm, args[1].trim()); 340 | String field = args[2].trim(); 341 | 342 | VMObject.VirtualObject vObj = (VMObject.VirtualObject) vm.heap.get(locals[obj]); 343 | if (vObj.fields.containsKey(field)) 344 | locals[val] = vObj.fields.get(field); 345 | else 346 | locals[val] = 0; 347 | } 348 | }); 349 | 350 | handlers.put("iget-object", handlers.get("iget")); 351 | handlers.put("iget-boolean", handlers.get("iget")); 352 | handlers.put("iget-byte", handlers.get("iget")); 353 | handlers.put("iget-char", handlers.get("iget")); 354 | handlers.put("iget-short", handlers.get("iget")); 355 | 356 | handlers.put("iput", new OpHandler() { 357 | @Override 358 | public void handle(QuickVM vm, String line) throws IOException { 359 | int[] locals = vm.stack.peek().locals; 360 | String[] args = line.split("\\s+", 2)[1].split(",", 3); 361 | int val = parseRegister(vm, args[0].trim()); 362 | int obj = parseRegister(vm, args[1].trim()); 363 | String field = args[2].trim(); 364 | String type = field.split(":", 2)[1]; 365 | 366 | VMObject.VirtualObject vObj = (VMObject.VirtualObject) vm.heap.get(locals[obj]); 367 | vObj.fields.put(field, locals[val]); 368 | } 369 | }); 370 | 371 | handlers.put("iput-object", handlers.get("iput")); 372 | handlers.put("iput-boolean", handlers.get("iput")); 373 | handlers.put("iput-byte", handlers.get("iput")); 374 | handlers.put("iput-char", handlers.get("iput")); 375 | handlers.put("iput-short", handlers.get("iput")); 376 | 377 | handlers.put("sget", new OpHandler() { 378 | @Override 379 | public void handle(QuickVM vm, String line) throws IOException { 380 | int[] locals = vm.stack.peek().locals; 381 | String[] args = line.split("\\s+", 2)[1].split(",", 2); 382 | int val = parseRegister(vm, args[0].trim()); 383 | String field = args[1].trim(); 384 | 385 | if (vm.staticFields.containsKey(field)) 386 | locals[val] = vm.staticFields.get(field); 387 | else 388 | locals[val] = 0; 389 | } 390 | }); 391 | 392 | handlers.put("sget-object", handlers.get("sget")); 393 | handlers.put("sget-boolean", handlers.get("sget")); 394 | handlers.put("sget-byte", handlers.get("sget")); 395 | handlers.put("sget-char", handlers.get("sget")); 396 | handlers.put("sget-short", handlers.get("sget")); 397 | 398 | handlers.put("sput", new OpHandler() { 399 | @Override 400 | public void handle(QuickVM vm, String line) throws IOException { 401 | int[] locals = vm.stack.peek().locals; 402 | String[] args = line.split("\\s+", 2)[1].split(",", 2); 403 | int val = parseRegister(vm, args[0].trim()); 404 | String field = args[1].trim(); 405 | String type = field.split(":", 2)[1]; 406 | 407 | if (type.charAt(0) == 'L' || type.charAt(0) == '[') { 408 | VMObject obj = vm.heap.get(locals[val]); 409 | System.out.printf("%s = %s\n", field, obj); 410 | if (obj != null && !obj.getType().equals(type)) 411 | System.err.println("Warning! Types don't match: " + obj + " for " + field); 412 | } else { 413 | System.out.printf("%s = %s\n", field, locals[val]); 414 | } 415 | vm.staticFields.put(field, locals[val]); 416 | } 417 | }); 418 | 419 | handlers.put("sput-object", handlers.get("sput")); 420 | handlers.put("sput-boolean", handlers.get("sput")); 421 | handlers.put("sput-byte", handlers.get("sput")); 422 | handlers.put("sput-char", handlers.get("sput")); 423 | handlers.put("sput-short", handlers.get("sput")); 424 | 425 | handlers.put("aget", new OpHandler() { 426 | @Override 427 | public void handle(QuickVM vm, String line) throws IOException { 428 | int[] locals = vm.stack.peek().locals; 429 | String[] args = line.split("\\s+", 2)[1].split(",", 3); 430 | int val = parseRegister(vm, args[0].trim()); 431 | int arr = parseRegister(vm, args[1].trim()); 432 | int idx = parseRegister(vm, args[2].trim()); 433 | 434 | VMObject.ArrayObject arrObj = (VMObject.ArrayObject) vm.heap.get(locals[arr]); 435 | locals[val] = arrObj.value[locals[idx]]; 436 | } 437 | }); 438 | 439 | handlers.put("aget-object", handlers.get("aget")); 440 | handlers.put("aget-boolean", handlers.get("aget")); 441 | handlers.put("aget-byte", handlers.get("aget")); 442 | handlers.put("aget-char", handlers.get("aget")); 443 | handlers.put("aget-short", handlers.get("aget")); 444 | 445 | handlers.put("aput", new OpHandler() { 446 | @Override 447 | public void handle(QuickVM vm, String line) throws IOException { 448 | int[] locals = vm.stack.peek().locals; 449 | String[] args = line.split("\\s+", 2)[1].split(",", 3); 450 | int val = parseRegister(vm, args[0].trim()); 451 | int arr = parseRegister(vm, args[1].trim()); 452 | int idx = parseRegister(vm, args[2].trim()); 453 | 454 | VMObject.ArrayObject arrObj = (VMObject.ArrayObject) vm.heap.get(locals[arr]); 455 | arrObj.value[locals[idx]] = locals[val]; 456 | } 457 | }); 458 | 459 | handlers.put("aput-object", handlers.get("aput")); 460 | handlers.put("aput-boolean", handlers.get("aput")); 461 | handlers.put("aput-byte", handlers.get("aput")); 462 | handlers.put("aput-char", handlers.get("aput")); 463 | handlers.put("aput-short", handlers.get("aput")); 464 | 465 | handlers.put("array-length", new OpHandler() { 466 | @Override 467 | public void handle(QuickVM vm, String line) throws IOException { 468 | int[] locals = vm.stack.peek().locals; 469 | String[] args = line.split("\\s+", 2)[1].split(",", 2); 470 | int dst = parseRegister(vm, args[0].trim()); 471 | int arr = parseRegister(vm, args[1].trim()); 472 | 473 | VMObject.ArrayObject arrObj = (VMObject.ArrayObject) vm.heap.get(locals[arr]); 474 | locals[dst] = arrObj.value.length; 475 | } 476 | }); 477 | 478 | /** ARITHMETICS, BITWISE OPS, LENGTH MOD **/ 479 | 480 | handlers.put("add-int", new OpHandler() { 481 | @Override 482 | public void handle(QuickVM vm, String line) throws IOException { 483 | int[] locals = vm.stack.peek().locals; 484 | String[] args = line.split("\\s+", 2)[1].split(",", 3); 485 | int dst = parseRegister(vm, args[0].trim()); 486 | int o1 = readValue(vm, args[args.length - 2].trim()); 487 | int o2 = readValue(vm, args[args.length - 1].trim()); 488 | 489 | locals[dst] = o1 + o2; 490 | } 491 | }); 492 | 493 | handlers.put("rem-int", new OpHandler() { 494 | @Override 495 | public void handle(QuickVM vm, String line) throws IOException { 496 | int[] locals = vm.stack.peek().locals; 497 | String[] args = line.split("\\s+", 2)[1].split(",", 3); 498 | int dst = parseRegister(vm, args[0].trim()); 499 | int o1 = readValue(vm, args[args.length - 2].trim()); 500 | int o2 = readValue(vm, args[args.length - 1].trim()); 501 | 502 | locals[dst] = o1 % o2; 503 | } 504 | }); 505 | 506 | handlers.put("xor-int", new OpHandler() { 507 | @Override 508 | public void handle(QuickVM vm, String line) throws IOException { 509 | int[] locals = vm.stack.peek().locals; 510 | String[] args = line.split("\\s+", 2)[1].split(",", 3); 511 | int dst = parseRegister(vm, args[0].trim()); 512 | int o1 = readValue(vm, args[args.length - 2].trim()); 513 | int o2 = readValue(vm, args[args.length - 1].trim()); 514 | 515 | locals[dst] = o1 ^ o2; 516 | } 517 | }); 518 | 519 | handlers.put("int-to-char", new OpHandler() { 520 | @Override 521 | public void handle(QuickVM vm, String line) throws IOException { 522 | int[] locals = vm.stack.peek().locals; 523 | String[] args = line.split("\\s+", 2)[1].split(",", 2); 524 | int dst = parseRegister(vm, args[0].trim()); 525 | int src = parseRegister(vm, args[1].trim()); 526 | 527 | //FIXME: are overflowing casts defined in java? 528 | locals[dst] = (char) locals[src]; 529 | } 530 | }); 531 | 532 | /** MISCELLANEOUS **/ 533 | 534 | /*handlers.put("check-cast", new OpHandler() { 535 | @Override 536 | public void handle(QuickVM vm, String line) throws IOException { 537 | //FIXME: implement this when we support exceptions 538 | } 539 | });*/ 540 | 541 | } 542 | 543 | /* Parsing utilities */ 544 | 545 | public static int parseRegister(QuickVM vm, String reg) { 546 | if (reg.startsWith("v")) { 547 | return Integer.parseInt(reg.substring(1)); 548 | } 549 | if (reg.startsWith("p")) { 550 | Frame f = vm.stack.peek(); 551 | return Integer.parseInt(reg.substring(1)) + (f.locals.length - f.parameters); 552 | } 553 | throw new IllegalArgumentException("Invalid register string: "+reg); 554 | } 555 | 556 | public static String parseStringLiteral(String str) { 557 | if (!str.startsWith("\"") || !str.endsWith("\"")) 558 | throw new IllegalArgumentException("Invalid string literal"); 559 | return StringUtil.unescape_perl_string(str.substring(1, str.length()-1)); 560 | } 561 | 562 | public static long parseIntegerLiteral(String str) { 563 | long multiplier = 1; 564 | int radix = 10; 565 | 566 | if (str.startsWith("-") || str.startsWith("+")) { 567 | if (str.startsWith("-")) multiplier = -1; 568 | str = str.substring(1); 569 | } 570 | 571 | if (str.startsWith("0x")) { 572 | str = str.substring(2); 573 | radix = 16; 574 | } 575 | return multiplier * Long.parseLong(str, radix); 576 | } 577 | 578 | public static int readValue(QuickVM vm, String str) { 579 | try { 580 | return vm.stack.peek().locals[parseRegister(vm, str)]; 581 | } catch (IllegalArgumentException ex) { 582 | return (int) parseIntegerLiteral(str); 583 | } 584 | } 585 | 586 | } 587 | -------------------------------------------------------------------------------- /src/quickvm/QuickVM.java: -------------------------------------------------------------------------------- 1 | package quickvm; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.File; 5 | import java.io.FileNotFoundException; 6 | import java.io.FileReader; 7 | import java.io.IOException; 8 | import java.io.Reader; 9 | import java.util.ArrayDeque; 10 | import java.util.ArrayList; 11 | import java.util.Deque; 12 | import java.util.HashMap; 13 | import java.util.HashSet; 14 | import java.util.List; 15 | import java.util.Map; 16 | import java.util.NoSuchElementException; 17 | import java.util.Scanner; 18 | import java.util.Set; 19 | import java.util.logging.Level; 20 | import java.util.logging.Logger; 21 | import static quickvm.util.ReaderUtil.readLine; 22 | import static quickvm.util.StringUtil.stripComment; 23 | 24 | /** 25 | * Barebones Smali VM. Probably the dirtiest code I've ever written. 26 | * DOESN'T EVEN HAVE GC. NOT EVEN A GC. And it assumes the bytecode given 27 | * is well-formed and verifies on a real VM. 28 | * 29 | * Does not support exceptions, virtual methods nor 64-bit operations at the moment. 30 | */ 31 | public class QuickVM { 32 | 33 | public static interface OpHandler { 34 | void handle(QuickVM vm, String line) throws IOException; 35 | } 36 | 37 | public static interface MethodHandler { 38 | void handle(QuickVM vm, int[] params); 39 | } 40 | 41 | public static class Frame { 42 | /** 43 | * Reader, shifted right at the start of the new instruction to execute 44 | * at this scope. Reader should be marked after start of scope. 45 | */ 46 | Reader source; 47 | 48 | /** Local registers. */ 49 | int[] locals; 50 | 51 | /** Parameters scope was invoked with. */ 52 | int parameters; 53 | 54 | /** Scope name (method name). */ 55 | String name; 56 | } 57 | 58 | /** Call stack. */ 59 | Deque stack = new ArrayDeque<>(); 60 | 61 | /** Root directory where smali is stored. */ 62 | File root; 63 | 64 | /** Return register. */ 65 | int returnValue; 66 | 67 | /** Op handlers. */ 68 | Map handlers = new HashMap<>(); 69 | 70 | /** Native methods. */ 71 | Map methods = new HashMap<>(); 72 | 73 | /** Static fields. */ 74 | Map staticFields = new HashMap<>(); 75 | 76 | /** Object "heap". */ 77 | List heap = new ArrayList<>(); 78 | 79 | /** Initialized classes. */ 80 | Set initializedClasses = new HashSet<>(); 81 | 82 | /** Last executed (not necessarily completed) command. */ 83 | String lastCommand; 84 | 85 | public void jumpToLabel(String label) throws IOException { 86 | Reader source = stack.peek().source; 87 | source.reset(); 88 | while (true) { 89 | String line = readLine(source).trim(); 90 | if (line.equals(":"+label)) break; 91 | } 92 | } 93 | 94 | public boolean initializeType(String className) throws IOException { 95 | if (initializedClasses.contains(className)) return false; 96 | try { 97 | openScope("L" + className + ";->()V", new int[0]); 98 | return true; 99 | } catch (IllegalArgumentException ex) { 100 | if (!ex.getMessage().equals("Method not found")) throw ex; 101 | } 102 | return false; 103 | } 104 | 105 | public void openScope(String name, int[] params) throws IOException { 106 | if (methods.containsKey(name)) { 107 | methods.get(name).handle(this, params); 108 | return; 109 | } 110 | if (name.charAt(0) == '[') { 111 | methods.get("[->" + name.split("->", 2)[1]).handle(this, params); 112 | return; 113 | } 114 | if (name.charAt(0) != 'L') 115 | throw new IllegalArgumentException("What the fuck"); 116 | 117 | if (stack.size() > 32) 118 | throw new IllegalStateException("Too much fucking recursion"); 119 | 120 | String className = name.split(";")[0].substring(1); 121 | String methodName = name.split("->")[1]; 122 | File file = new File(root, className + ".smali"); 123 | 124 | Reader source = null; 125 | //try { 126 | try { 127 | source = new BufferedReader(new FileReader(file)); 128 | } catch (FileNotFoundException ex) { 129 | throw new IllegalArgumentException("Class not found for: " + name, ex); 130 | } 131 | 132 | 133 | while (true) { 134 | String line_ = readLine(source); 135 | if (line_ == null) throw new IllegalArgumentException("Method not found"); 136 | String[] line = line_.trim().split("\\s+"); 137 | if (line[0].equals(".method") && line[line.length-1].equals(methodName)) 138 | break; 139 | } 140 | 141 | int regs; 142 | String[] line = readLine(source).trim().split("\\s+", 2); 143 | if (line[0].equals(".locals")) { 144 | regs = params.length + Integer.parseInt(line[1]); 145 | } else if (line[0].equals(".registers")) { 146 | regs = Integer.parseInt(line[1]); 147 | } else { 148 | throw new IllegalArgumentException("Expected .locals or .registers"); 149 | } 150 | source.mark(0x10000); 151 | 152 | Frame f = new Frame(); 153 | f.name = name; 154 | f.locals = new int[regs]; 155 | f.parameters = params.length; 156 | System.arraycopy(params, 0, f.locals, f.locals.length - params.length, params.length); 157 | f.source = source; 158 | stack.push(f); 159 | source = null; 160 | /*} finally { 161 | if (source != null) source.close(); 162 | }*/ 163 | 164 | if (methodName.equals("()V")) 165 | initializedClasses.add(className); 166 | else 167 | initializeType(className); 168 | } 169 | 170 | public void closeScope() throws IOException { 171 | stack.pop().source.close(); 172 | } 173 | 174 | public void step() throws IOException { 175 | Reader source = stack.peek().source; 176 | while (true) { 177 | int c = source.read(); 178 | if (c == -1) throw new IllegalArgumentException("Unexpected end of file"); 179 | if (Character.isWhitespace(c) || c == '#') continue; 180 | 181 | // Skip labels 182 | if (c == ':') { 183 | readLine(source); 184 | continue; 185 | } 186 | 187 | // Process everything else 188 | String line = ((char)c) + readLine(source); 189 | lastCommand = line; 190 | line = stripComment(line).trim(); 191 | String name = line.split("\\s+")[0].split("/", 2)[0].trim(); 192 | if (!handlers.containsKey(name)) 193 | throw new IllegalArgumentException("Unknown command:\n" + line); 194 | handlers.get(name).handle(this, line); 195 | break; 196 | } 197 | } 198 | 199 | public int allocateObject(VMObject obj) { 200 | int idx = heap.size(); 201 | heap.add(obj); 202 | return idx; 203 | } 204 | 205 | public void reset() throws IOException { 206 | while (!stack.isEmpty()) closeScope(); 207 | staticFields.clear(); 208 | heap.clear(); 209 | heap.add(null); 210 | initializedClasses.clear(); 211 | returnValue = 0; 212 | } 213 | 214 | public void dumpState() { 215 | System.err.println(" Last command:\n " + lastCommand); 216 | for (Frame f : stack) { 217 | System.err.printf("\n * %s\n Registers:", f.name); 218 | int regslength = 0; 219 | for (int i = 0; i < f.locals.length; i++) { 220 | if (f.locals[i] != 0) regslength = i+1; 221 | } 222 | for (int i = 0; i < regslength; i++) { 223 | if (i % 5 == 0) System.err.printf("\n "); 224 | System.err.printf(" %08x", f.locals[i]); 225 | } 226 | System.err.printf("\n Source:\n"); 227 | try { 228 | System.err.println(" | " + readLine(f.source)); 229 | System.err.println(" | " + readLine(f.source)); 230 | System.err.println(" | " + readLine(f.source)); 231 | } catch (IOException ex1) { 232 | Logger.getLogger(QuickVM.class.getName()).log(Level.SEVERE, null, ex1); 233 | } 234 | System.err.println(" ..."); 235 | } 236 | } 237 | 238 | 239 | 240 | public final static boolean VERBOSE = true; 241 | 242 | public static void main(String[] args) { 243 | Scanner sc = new Scanner(System.in); 244 | 245 | QuickVM vm = new QuickVM(); 246 | vm.root = new File(args[0]); 247 | Ops.addAll(vm.handlers); 248 | Methods.addAll(vm.methods); 249 | vm.heap.add(null); 250 | 251 | int total = 0, failed = 0; 252 | while (true) { 253 | String file; 254 | try { 255 | file = sc.nextLine(); 256 | } catch (NoSuchElementException ex) { 257 | break; 258 | } 259 | System.err.println("Trying file: " + file); 260 | total++; 261 | 262 | try { 263 | if (!file.endsWith(".smali")) throw new IllegalArgumentException("Expected smali file path"); 264 | vm.initializeType(file.substring(0, file.length() - 6)); 265 | while (vm.stack.size() > 0) vm.step(); 266 | } catch (Exception ex) { 267 | if (VERBOSE) { 268 | System.err.println(); 269 | Logger.getLogger(QuickVM.class.getName()).log(Level.SEVERE, null, ex); 270 | System.err.println("VM state:"); 271 | vm.dumpState(); 272 | } 273 | System.err.println("File failed: " + file); 274 | failed++; 275 | } 276 | try { 277 | vm.reset(); 278 | } catch (IOException ex) { 279 | Logger.getLogger(QuickVM.class.getName()).log(Level.SEVERE, null, ex); 280 | } 281 | } 282 | 283 | System.err.printf("Finished: %d classes from %d failed to load (%.2f%%).\n", failed, total, (failed / (float) total) * 100); 284 | System.err.println("Sanity tests you could perform:\n - Check for type cast warnings on stderr.\n - Check for \\u00 escapes on the constants.\n - Manually verify failed classes.\n - Check for string constants outside static constructors."); 285 | } 286 | 287 | } 288 | -------------------------------------------------------------------------------- /src/quickvm/VMObject.java: -------------------------------------------------------------------------------- 1 | package quickvm; 2 | 3 | import java.util.ArrayList; 4 | import java.util.HashMap; 5 | import java.util.HashSet; 6 | import java.util.Map; 7 | import quickvm.util.StringUtil; 8 | 9 | /** 10 | * VM object, allocated in the "heap" and pointable. 11 | */ 12 | public interface VMObject { 13 | 14 | public String getType(); 15 | 16 | /** 17 | * "Real" VM object, aka with field registers. 18 | */ 19 | public static class VirtualObject implements VMObject { 20 | public final QuickVM vm; 21 | public final String type; 22 | Map fields = new HashMap<>(); 23 | 24 | public VirtualObject(QuickVM vm, String type) { 25 | this.vm = vm; 26 | this.type = type; 27 | } 28 | 29 | @Override 30 | public String toString() { 31 | return type + "{" + "fields=" + fields + '}'; 32 | } 33 | 34 | @Override 35 | public String getType() { 36 | return type; 37 | } 38 | } 39 | 40 | /** 41 | * String objects are allocated natively for speed and simplicity. 42 | */ 43 | public class StringObject implements VMObject { 44 | public String value; 45 | 46 | public StringObject(String value) { 47 | this.value = value; 48 | } 49 | 50 | @Override 51 | public String toString() { 52 | return StringUtil.escapeString(value); 53 | } 54 | 55 | @Override 56 | public String getType() { 57 | return "Ljava/lang/String;"; 58 | } 59 | } 60 | 61 | /** 62 | * StringBuilder objects are allocated natively for speed and simplicity. 63 | */ 64 | public class StringBuilderObject implements VMObject { 65 | public StringBuilder value = new StringBuilder(); 66 | 67 | @Override 68 | public String toString() { 69 | return "StringBuilder{ " + StringUtil.escapeString(value.toString()) + " }"; 70 | } 71 | 72 | @Override 73 | public String getType() { 74 | return "Ljava/lang/StringBuilder;"; 75 | } 76 | } 77 | 78 | /** 79 | * ArrayList objects are allocated natively for speed and simplicity. 80 | */ 81 | public class ArrayListObject implements VMObject { 82 | public final QuickVM vm; 83 | public ArrayList value; 84 | 85 | public ArrayListObject(QuickVM vm) { 86 | this.vm = vm; 87 | } 88 | 89 | @Override 90 | public String toString() { 91 | StringBuilder sb = new StringBuilder(); 92 | sb.append("ArrayList[ "); 93 | for (int i = 0; i < value.size(); i++) { 94 | if (i > 0) sb.append(", "); 95 | sb.append(vm.heap.get(value.get(i))); 96 | } 97 | sb.append(" ]"); 98 | return sb.toString(); 99 | } 100 | 101 | @Override 102 | public String getType() { 103 | return "Ljava/util/ArrayList;"; 104 | } 105 | } 106 | 107 | /** 108 | * HashMap objects are allocated natively for speed and simplicity. 109 | * TODO: use hashcode 110 | */ 111 | public class HashMapObject implements VMObject { 112 | public final QuickVM vm; 113 | public HashMap value; 114 | 115 | public HashMapObject(QuickVM vm) { 116 | this.vm = vm; 117 | } 118 | 119 | @Override 120 | public String toString() { 121 | StringBuilder sb = new StringBuilder(); 122 | sb.append("HashMap{ "); 123 | boolean emitted = false; 124 | for (Integer key : value.keySet()) { 125 | if (emitted) sb.append(", "); 126 | emitted = true; 127 | sb.append(vm.heap.get(key)); 128 | sb.append("="); 129 | sb.append(vm.heap.get(value.get(key))); 130 | } 131 | sb.append(" }"); 132 | return sb.toString(); 133 | } 134 | 135 | @Override 136 | public String getType() { 137 | return "Ljava/util/HashMap;"; 138 | } 139 | } 140 | 141 | /** 142 | * HashSet objects are allocated natively for speed and simplicity. 143 | * TODO: use hashcode 144 | */ 145 | public class HashSetObject implements VMObject { 146 | public final QuickVM vm; 147 | public HashSet value; 148 | 149 | public HashSetObject(QuickVM vm) { 150 | this.vm = vm; 151 | } 152 | 153 | @Override 154 | public String toString() { 155 | StringBuilder sb = new StringBuilder(); 156 | sb.append("HashSet{ "); 157 | boolean emitted = false; 158 | for (Integer key : value) { 159 | if (emitted) sb.append(", "); 160 | emitted = true; 161 | sb.append(vm.heap.get(key)); 162 | } 163 | sb.append(" }"); 164 | return sb.toString(); 165 | } 166 | 167 | @Override 168 | public String getType() { 169 | return "Ljava/util/HashSet;"; 170 | } 171 | } 172 | 173 | /** 174 | * Array objects are allocated natively, of course. 175 | */ 176 | public class ArrayObject implements VMObject { 177 | public final QuickVM vm; 178 | public final String type; 179 | public final int[] value; 180 | 181 | public ArrayObject(QuickVM vm, String type, int[] value) { 182 | this.vm = vm; 183 | this.type = type; 184 | this.value = value; 185 | } 186 | 187 | @Override 188 | public String toString() { 189 | StringBuilder sb = new StringBuilder(); 190 | sb.append("[ "); 191 | for (int i = 0; i < value.length; i++) { 192 | if (i > 0) sb.append(", "); 193 | int val = value[i]; 194 | if (type.charAt(1) == 'L' || type.charAt(1) == '[') { 195 | sb.append(vm.heap.get(val)); 196 | } else { 197 | sb.append(val); 198 | } 199 | } 200 | sb.append(" ]"); 201 | return sb.toString(); 202 | } 203 | 204 | @Override 205 | public String getType() { 206 | return type; 207 | } 208 | 209 | } 210 | 211 | } 212 | -------------------------------------------------------------------------------- /src/quickvm/util/ReaderUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this license header, choose License Headers in Project Properties. 3 | * To change this template file, choose Tools | Templates 4 | * and open the template in the editor. 5 | */ 6 | package quickvm.util; 7 | 8 | import java.io.IOException; 9 | import java.io.Reader; 10 | 11 | /** 12 | * 13 | * @author xavier 14 | */ 15 | public class ReaderUtil { 16 | 17 | public static String readLine(Reader r) throws IOException { 18 | String str = ""; // Inefficient as fuck, but who cares 19 | while (true) { 20 | int c = r.read(); 21 | if (c == -1 && str.isEmpty()) return null; 22 | if (c == -1 || c == '\n') break; 23 | str += (char) c; 24 | } 25 | return str; 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/quickvm/util/StringUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this license header, choose License Headers in Project Properties. 3 | * To change this template file, choose Tools | Templates 4 | * and open the template in the editor. 5 | */ 6 | package quickvm.util; 7 | 8 | /** 9 | * Credits to @tchrist: https://stackoverflow.com/questions/3537706/how-to-unescape-a-java-string-literal-in-java 10 | */ 11 | public class StringUtil { 12 | 13 | /* 14 | * 15 | * unescape_perl_string() 16 | * 17 | * Tom Christiansen 18 | * Sun Nov 28 12:55:24 MST 2010 19 | * 20 | * It's completely ridiculous that there's no standard 21 | * unescape_java_string function. Since I have to do the 22 | * damn thing myself, I might as well make it halfway useful 23 | * by supporting things Java was too stupid to consider in 24 | * strings: 25 | * 26 | * => "?" items are additions to Java string escapes 27 | * but normal in Java regexes 28 | * 29 | * => "!" items are also additions to Java regex escapes 30 | * 31 | * Standard singletons: ?\a ?\e \f \n \r \t 32 | * 33 | * NB: \b is unsupported as backspace so it can pass-through 34 | * to the regex translator untouched; I refuse to make anyone 35 | * doublebackslash it as doublebackslashing is a Java idiocy 36 | * I desperately wish would die out. There are plenty of 37 | * other ways to write it: 38 | * 39 | * \cH, \12, \012, \x08 \x{8}, \u0008, \U00000008 40 | * 41 | * Octal escapes: \0 \0N \0NN \N \NN \NNN 42 | * Can range up to !\777 not \377 43 | * 44 | * TODO: add !\o{NNNNN} 45 | * last Unicode is 4177777 46 | * maxint is 37777777777 47 | * 48 | * Control chars: ?\cX 49 | * Means: ord(X) ^ ord('@') 50 | * 51 | * Old hex escapes: \xXX 52 | * unbraced must be 2 xdigits 53 | * 54 | * Perl hex escapes: !\x{XXX} braced may be 1-8 xdigits 55 | * NB: proper Unicode never needs more than 6, as highest 56 | * valid codepoint is 0x10FFFF, not maxint 0xFFFFFFFF 57 | * 58 | * Lame Java escape: \[IDIOT JAVA PREPROCESSOR]uXXXX must be 59 | * exactly 4 xdigits; 60 | * 61 | * I can't write XXXX in this comment where it belongs 62 | * because the damned Java Preprocessor can't mind its 63 | * own business. Idiots! 64 | * 65 | * Lame Python escape: !\UXXXXXXXX must be exactly 8 xdigits 66 | * 67 | * TODO: Perl translation escapes: \Q \U \L \E \[IDIOT JAVA PREPROCESSOR]u \l 68 | * These are not so important to cover if you're passing the 69 | * result to Pattern.compile(), since it handles them for you 70 | * further downstream. Hm, what about \[IDIOT JAVA PREPROCESSOR]u? 71 | * 72 | */ 73 | 74 | public final static 75 | String unescape_perl_string(String oldstr) { 76 | 77 | /* 78 | * In contrast to fixing Java's broken regex charclasses, 79 | * this one need be no bigger, as unescaping shrinks the string 80 | * here, where in the other one, it grows it. 81 | */ 82 | 83 | StringBuffer newstr = new StringBuffer(oldstr.length()); 84 | 85 | boolean saw_backslash = false; 86 | 87 | for (int i = 0; i < oldstr.length(); i++) { 88 | int cp = oldstr.codePointAt(i); 89 | if (oldstr.codePointAt(i) > Character.MAX_VALUE) { 90 | i++; /****WE HATES UTF-16! WE HATES IT FOREVERSES!!!****/ 91 | } 92 | 93 | if (!saw_backslash) { 94 | if (cp == '\\') { 95 | saw_backslash = true; 96 | } else { 97 | newstr.append(Character.toChars(cp)); 98 | } 99 | continue; /* switch */ 100 | } 101 | 102 | if (cp == '\\') { 103 | saw_backslash = false; 104 | newstr.append('\\'); 105 | continue; /* switch */ 106 | } 107 | 108 | switch (cp) { 109 | 110 | case 'r': newstr.append('\r'); 111 | break; /* switch */ 112 | 113 | case '"': newstr.append('"'); 114 | break; /* switch */ 115 | 116 | case '\'': newstr.append('\''); 117 | break; /* switch */ 118 | 119 | case 'n': newstr.append('\n'); 120 | break; /* switch */ 121 | 122 | case 'f': newstr.append('\f'); 123 | break; /* switch */ 124 | 125 | /* PASS a \b THROUGH!! */ 126 | case 'b': newstr.append("\\b"); 127 | break; /* switch */ 128 | 129 | case 't': newstr.append('\t'); 130 | break; /* switch */ 131 | 132 | case 'a': newstr.append('\007'); 133 | break; /* switch */ 134 | 135 | case 'e': newstr.append('\033'); 136 | break; /* switch */ 137 | 138 | /* 139 | * A "control" character is what you get when you xor its 140 | * codepoint with '@'==64. This only makes sense for ASCII, 141 | * and may not yield a "control" character after all. 142 | * 143 | * Strange but true: "\c{" is ";", "\c}" is "=", etc. 144 | */ 145 | case 'c': { 146 | if (++i == oldstr.length()) { die("trailing \\c"); } 147 | cp = oldstr.codePointAt(i); 148 | /* 149 | * don't need to grok surrogates, as next line blows them up 150 | */ 151 | if (cp > 0x7f) { die("expected ASCII after \\c"); } 152 | newstr.append(Character.toChars(cp ^ 64)); 153 | break; /* switch */ 154 | } 155 | 156 | case '8': 157 | case '9': die("illegal octal digit"); 158 | /* NOTREACHED */ 159 | 160 | /* 161 | * may be 0 to 2 octal digits following this one 162 | * so back up one for fallthrough to next case; 163 | * unread this digit and fall through to next case. 164 | */ 165 | case '1': 166 | case '2': 167 | case '3': 168 | case '4': 169 | case '5': 170 | case '6': 171 | case '7': --i; 172 | /* FALLTHROUGH */ 173 | 174 | /* 175 | * Can have 0, 1, or 2 octal digits following a 0 176 | * this permits larger values than octal 377, up to 177 | * octal 777. 178 | */ 179 | case '0': { 180 | if (i+1 == oldstr.length()) { 181 | /* found \0 at end of string */ 182 | newstr.append(Character.toChars(0)); 183 | break; /* switch */ 184 | } 185 | i++; 186 | int digits = 0; 187 | int j; 188 | for (j = 0; j <= 2; j++) { 189 | if (i+j == oldstr.length()) { 190 | break; /* for */ 191 | } 192 | /* safe because will unread surrogate */ 193 | int ch = oldstr.charAt(i+j); 194 | if (ch < '0' || ch > '7') { 195 | break; /* for */ 196 | } 197 | digits++; 198 | } 199 | if (digits == 0) { 200 | --i; 201 | newstr.append('\0'); 202 | break; /* switch */ 203 | } 204 | int value = 0; 205 | try { 206 | value = Integer.parseInt( 207 | oldstr.substring(i, i+digits), 8); 208 | } catch (NumberFormatException nfe) { 209 | die("invalid octal value for \\0 escape"); 210 | } 211 | newstr.append(Character.toChars(value)); 212 | i += digits-1; 213 | break; /* switch */ 214 | } /* end case '0' */ 215 | 216 | case 'x': { 217 | if (i+2 > oldstr.length()) { 218 | die("string too short for \\x escape"); 219 | } 220 | i++; 221 | boolean saw_brace = false; 222 | if (oldstr.charAt(i) == '{') { 223 | /* ^^^^^^ ok to ignore surrogates here */ 224 | i++; 225 | saw_brace = true; 226 | } 227 | int j; 228 | for (j = 0; j < 8; j++) { 229 | 230 | if (!saw_brace && j == 2) { 231 | break; /* for */ 232 | } 233 | 234 | /* 235 | * ASCII test also catches surrogates 236 | */ 237 | int ch = oldstr.charAt(i+j); 238 | if (ch > 127) { 239 | die("illegal non-ASCII hex digit in \\x escape"); 240 | } 241 | 242 | if (saw_brace && ch == '}') { break; /* for */ } 243 | 244 | if (! ( (ch >= '0' && ch <= '9') 245 | || 246 | (ch >= 'a' && ch <= 'f') 247 | || 248 | (ch >= 'A' && ch <= 'F') 249 | ) 250 | ) 251 | { 252 | die(String.format( 253 | "illegal hex digit #%d '%c' in \\x", ch, ch)); 254 | } 255 | 256 | } 257 | if (j == 0) { die("empty braces in \\x{} escape"); } 258 | int value = 0; 259 | try { 260 | value = Integer.parseInt(oldstr.substring(i, i+j), 16); 261 | } catch (NumberFormatException nfe) { 262 | die("invalid hex value for \\x escape"); 263 | } 264 | newstr.append(Character.toChars(value)); 265 | if (saw_brace) { j++; } 266 | i += j-1; 267 | break; /* switch */ 268 | } 269 | 270 | case 'u': { 271 | if (i+4 > oldstr.length()) { 272 | die("string too short for \\u escape"); 273 | } 274 | i++; 275 | int j; 276 | for (j = 0; j < 4; j++) { 277 | /* this also handles the surrogate issue */ 278 | if (oldstr.charAt(i+j) > 127) { 279 | die("illegal non-ASCII hex digit in \\u escape"); 280 | } 281 | } 282 | int value = 0; 283 | try { 284 | value = Integer.parseInt( oldstr.substring(i, i+j), 16); 285 | } catch (NumberFormatException nfe) { 286 | die("invalid hex value for \\u escape"); 287 | } 288 | newstr.append(Character.toChars(value)); 289 | i += j-1; 290 | break; /* switch */ 291 | } 292 | 293 | case 'U': { 294 | if (i+8 > oldstr.length()) { 295 | die("string too short for \\U escape"); 296 | } 297 | i++; 298 | int j; 299 | for (j = 0; j < 8; j++) { 300 | /* this also handles the surrogate issue */ 301 | if (oldstr.charAt(i+j) > 127) { 302 | die("illegal non-ASCII hex digit in \\U escape"); 303 | } 304 | } 305 | int value = 0; 306 | try { 307 | value = Integer.parseInt(oldstr.substring(i, i+j), 16); 308 | } catch (NumberFormatException nfe) { 309 | die("invalid hex value for \\U escape"); 310 | } 311 | newstr.append(Character.toChars(value)); 312 | i += j-1; 313 | break; /* switch */ 314 | } 315 | 316 | default: newstr.append('\\'); 317 | newstr.append(Character.toChars(cp)); 318 | 319 | say(String.format( 320 | "DEFAULT unrecognized escape %c passed through", 321 | cp)); 322 | 323 | break; /* switch */ 324 | 325 | } 326 | saw_backslash = false; 327 | } 328 | 329 | /* weird to leave one at the end */ 330 | if (saw_backslash) { 331 | newstr.append('\\'); 332 | } 333 | 334 | return newstr.toString(); 335 | } 336 | 337 | /* 338 | * Return a string "U+XX.XXX.XXXX" etc, where each XX set is the 339 | * xdigits of the logical Unicode code point. No bloody brain-damaged 340 | * UTF-16 surrogate crap, just true logical characters. 341 | */ 342 | public final static 343 | String uniplus(String s) { 344 | if (s.length() == 0) { 345 | return ""; 346 | } 347 | /* This is just the minimum; sb will grow as needed. */ 348 | StringBuffer sb = new StringBuffer(2 + 3 * s.length()); 349 | sb.append("U+"); 350 | for (int i = 0; i < s.length(); i++) { 351 | sb.append(String.format("%X", s.codePointAt(i))); 352 | if (s.codePointAt(i) > Character.MAX_VALUE) { 353 | i++; /****WE HATES UTF-16! WE HATES IT FOREVERSES!!!****/ 354 | } 355 | if (i+1 < s.length()) { 356 | sb.append("."); 357 | } 358 | } 359 | return sb.toString(); 360 | } 361 | 362 | private static final 363 | void die(String foa) { 364 | throw new IllegalArgumentException(foa); 365 | } 366 | 367 | private static final 368 | void say(String what) { 369 | System.out.println(what); 370 | } 371 | 372 | public static String escapeString(String str) { 373 | StringBuilder sb = new StringBuilder("\""); 374 | for (char c : str.toCharArray()) { 375 | switch (c) { 376 | case '\\': sb.append("\\\\"); break; 377 | case '"': sb.append("\\\""); break; 378 | case '\t': sb.append("\\t"); break; 379 | case '\n': sb.append("\\n"); break; 380 | default: 381 | if (c < 0x20 || c == 0x7F) sb.append(String.format("\\u%04X", (int)c)); 382 | else sb.append(c); 383 | } 384 | } 385 | sb.append("\""); 386 | return sb.toString(); 387 | } 388 | 389 | public static String stripComment(String str) { 390 | int i = 0; 391 | boolean insideString = false; 392 | while (i < str.length()) { 393 | if (insideString) { 394 | if (str.charAt(i) == '\\') i++; 395 | else if (str.charAt(i) == '"') insideString = false; 396 | } else { 397 | if (str.charAt(i) == '#') break; 398 | else if (str.charAt(i) == '"') insideString = true; 399 | } 400 | i++; 401 | } 402 | return str.substring(0, i); 403 | } 404 | 405 | 406 | } 407 | --------------------------------------------------------------------------------