languages.md
December 24, 2019 ยท View on GitHub
Historical Context
- Back in the 50s, people were still programming in assembly. It was also at this time that programming languages were being developed left and right with the hope of reducing costs (programming in assembly was a tedious and time-consuming task and not to mention also a maintenance nightmare), but the transition to programming in a higher-level language was not as easy as the transition to programming in assembly from a decade earlier. The transition to programming in assembly was intuitive since it made the software development lifecycle faster, but most importantly, with zero performance penalty since an assembler translates one assembly instruction to exactly one machine code instruction. Programming in a higher-level language was a completely different story. A higher-level programming statement can be translated to multiple machine code instructions and different compiler implementations for the same language also meant different sets of machine code output. Early skepticism of programming langauges was inevitable since compiler was not able to generate code as efficient as hand-written assembly. One of the first compiler writers, Grace Hopper, was no stranger to such skepticism as she had said: "I had a running compiler and nobody would touch it...they carefully told me, computers could only do arithmetic; they could not do programs..." Fortunately over times, people slowly came into acceptance of programming languages since the upsides (reduced programming and maintenance costs) outvalue their downsides (performance). And through decades of compiler research since the 50s, with some mishaps along the way, modern compilers' outputs have become relatively efficient.
Catalyst For Modern Computing
- Programming in a higher-level language frees programmers from having to worry about how to interact with the underlying hardware. For example, in assembly there is no notion of variables, only registers and memory locations and to make it even harder to program you only have a limited numbers of general-purpose registers that you can modify. Programming languages abstract away those hardware interactions, allowing programmers to focus more on the computational task they wanted to solve. This, in my opinion, is the catalyst that leads to the proliferation of various computing sub-fields (e.g. web development, machine learning). A lot of what's possible today will not be availiable if we are still programming in assembly.
Different Languages' Implementations From A Reversing Perspective (Interpreted)
- Interpreted or compiled is a property of the langauge implementations. But since most languages have their implementations slanted toward one, interpreted or compiled becomes an association with the language itself. And to make the associations even more muddier, languages like Python and Ruby that are typically associated as being interpreted are not truly interpreted. Rather, they are compiled down to an intermediate form called bytecode and then interpreted during runtime by an interpreter. Usually when we think of compiled languages we think of languages like C or C++ that uses ahead-of-time compilation to translate high-level source files to native machine code instructions. But in fact, language like Python is also compiled, just not all the way down to native machine code. So what does this means to a reverse engineer? (1) When you are reversing an executable file, not all of them will contain native machine code corresponding to x86, x64, or ARM instruction set. (2) A bad reversing habit is to drop an executable file you wanted to analyze into a disassembler like IDA first without a second thought. IDA may still be the best universal disassembler out there, but it is actually not that great at disassembling bytecode. Instead, use a disassembler targeting that specific interpreted language. For example, with compiled Python (.pyc) use the built-in dis module. (3) Since they are only compiled down to an intermediate form, a lot less information from the source level is lost compared to those that get compiled down to machine code. This is why a decompiler targeting an interpreted language like Python or .NET works better than a decompiler targeting an ahead-of-time compiled language like C.
Different Languages' Implementations From A Reversing Perspective (Compiled)
- Although ahead-of-time compiled languages targeting the same CPU all compile down to the same instruction set, their outputs differ greatly in that they are still idiosyncratic to the original source languages. For example, because C++ supports method overloading its function names will be mangled and since C doesn't support method overloading it will be easy to identify if a compiled code is compiled from C++ by just looking at IDA's function window. If you can, you always want to try to identify the original source language. Knowing the source language makes it easier for you to relate a block of assembly code back to a higher level construct, allowing you to piece together what the assembly code is doing quicker.
section overview
further readings
- Compiler Explorer: a compiler explorer, for languages such as C, C++, GO, and many more, that lets you explore the assembly listings generated by your choice of compiler. What makes this web application even more awesome is that you can specify the exact compiler version with compiler options
- Early High-Level Programming Languages