What follows are a few quick-and-general pointers on "I want to start doing
lower level stuff, but need a motivating direction for a starter project."
They're somewhat un-tested because I haven't mentored any apps-to-systems
transitions, but, as somebody who plays on both sides of that fence, I think
they all sound pretty fun.
A word of warning: systems programming may feel crude at first compared
to the managed languages and application-level design you're used to. However,
even among experts, the prevalence of footguns motivates simple designs and APIs, which can be a beautiful thing.
As a heuristic, when starting out, just code it the simple, ungeneralized way.
If you're doing something interesting, hard problems are likely to present
Check out sites like hackaday.com to see the incredible feats that
people accomplish through microcontrollers and hobby time.
When starting out, it's great to get the tactile feedback of lighting up a
bright blue LED or successfully sending that first UDP packet to your desktop
at four in the morning.
Microcontroller-based development is also nice because you can build up your understanding of C code, if you're feeling rusty, from basic usage — say, keeping everything you need to store as a global variable or array — to fancier techniques as you improve and gain experience with what works well.
Although I haven't played with them specifically, I understand that Arduino
boards are all the rage these days — there are great tutorials and support
communities out on the web that love to help newbies get started with
microcontrollers. AVR freaks was around even when I was programming on my
STK500. I would recommend reading some forums to figure out which board looks right for you and your intended projects.
At school, people really took to Bruce Land's microcontroller class,
because you can't help but feel the fiero as you work towards more and more
ambitious project goals.
Since that class is still being taught, look to
the exercises and projects (link above) as good examples of what's possible with bright
students and four credits worth of time. [*]
Start fixing bugs on low-level open source projects
Many open source projects love to see willing new contributors. Especially check out projects a) that are known for having good/friendly mentoring and
b) that you think are cool (which will help you stay motivated).
I know one amazing person I worked with at Mozilla got into the project by
taking his time to figure out how to properly patch some open bugs.
If you take that route, either compare your patch to what the project
member has already posted, or request that somebody give you feedback on your
This is another good way to pick up mentor-like connections.
Check out open courseware for conceptual background
I personally love the rapid evolution of open courseware we're seeing. If you're feeling confident, pick a random low-level thing you've heard-of-but-never-quite-understood, type it into a search engine, and do a deep dive on a lecture or series. If you want a more structured approach, a simple search for systems programming open courseware has quite educational looking results.
General specifics: OSes and reversing
If you're really into OSes, I think you should just dive in and try writing a little kernel on top of your hardware of choice in qemu (a hardware emulator). Quick searches turn up some seemingly excellent tutorials on writing simple OS kernels on qemu, and writing simple OSes for microcontrollers is often a student project topic in courses like the one I mention above. [†]
With some confidence, patience, maybe a programming guide, and recall of some low-level background from school, I think this should be doable. Some research will be required on effective methods of debugging, though — that's always the trick with bare metal coding.
Or, for something less audacious sounding: build your own Linux kernel with some modifications to figure out what's going on.
There are plenty of guides on how to do this for your Linux distribution of choice, and you can learn a great deal just by fiddling around with code paths and using printk.
Try doing something on the system (in userspace) that's simple to isolate in the kernel source using grep — like mmapping /dev/mem or accessing an entry in /proc — to figure out how it works, and leave no stone unturned.
I recommend taking copious notes, because I find that's the best way to trace out any complex system. Taking notes makes it easy to refer back to previous realizations and backtrack at will.
Read everything that interests you on Linux Kernel Newbies, and subscribe to kernel changelog summaries. Attempt to understand things that interest you in the source tree's /Documentation. Write a really simple Linux Kernel Module. Then, refer to freely available texts for help in making it do progressively more interesting things. Another favorite read of mine was Understanding the Linux Kernel, if you have a hobby budget or a local library that carries it.
This I know less about — pretty much everybody I know that has done significant reversing is an IDA wizard, and I, at this point, am not. They are also typically Win32 experts, which I am not. Understanding obfuscated assembly is probably a lot easier with powerful and scriptable tools of that sort, which ideally also have a good understanding of the OS. [‡]
However, one of the things that struck me when I was doing background research for attack mitigation patches was how great the security community was at sharing information through papers, blog entries, and proof of concept code. Also, I found that there are a good number of videos online where security researchers share their insights and methods in the exploit analysis process. Video searches may turn up useful conference proceedings, or it may be more effective to work from the other direction: find conferences that deal with your topic of interest, and see which of those offer video recordings.
During my research on security-related things, a blog entry by Chris Rohlf caused Practical Malware Analysis to end up on my wishlist as an introductory text. Seems to have good reviews all around. Something else to check out on a trip to the library or online forums, perhaps.
One other neat thing we occassionally used for debugging at Mozilla was a VMWare-based time-traveling virtual machine instance. It sounded like they were deprecating it a few years back, so I'm not sure the status of it, but if it's still around it would literally allow you to play programs backwards!
As a guy who writes code, there are a few basic things I ask before I jump into any project:
Will I learn?
Do the people rock?
Will it ship?
Does it matter?
I guarantee that you'll learn from working on SpiderMonkey. It's an important language implementation created by some of the most brilliant and approachable coders I've ever had the privilege of working with.
We ship like clockwork. When a patch gets submitted to trunk, it's in the Firefox release 18 weeks later.
Hack: this technology could fall into the right hands [image]
And when it comes to finding meaning in your work, Mozilla makes life easy.
In my opinion, the Mozilla mission is technological utopianism at its finest. If you believe in using your technological superpowers to help people, Mozilla is for you.
If you know how to write and debug C++ code, you have the skills to hack SpiderMonkey. We don't go crazy with the C++, so C coders should also feel pretty confident. The only tools required are the ones necessary to build Firefox.
SpiderMonkey is a language implementation, but don't let that get to you. Once you get your hands dirty (say, by fixing a few minor bugs) you'll realize that language VMs are no different from the other systems-level programs that you know and love.
The JS team tries to tag good first bugs. If you see a good first bug that interests you, feel free to go in and make a comment stating your interest.
If you'd like to ease into development a little more, you can check out the latest ECMAScript specification and use that to create tests for our test suite. This is a great way to ensure SpiderMonkey engine quality and cross-browser compatibility.
In typical open-source style, once you've found something that interests you, hack away!
And feel free to sample from the buffet: every bug that you work on teaches you about a different aspect of the engine.
You may also stumble onto a part of the engine that you'd like to specialize in — we loving having domain experts hacking on our code as well!
When you improve the engine, you can get your name added to about:credits, in a product that ships to something like half a billion users, which I think is pretty cool.
Lots of great details and walkthroughs are available on the "New to SpiderMonkey" wiki page.
Barrel (#coding, #jsapi)
Friendly people hang around in these IRC channels at irc.mozilla.org. #coding is for general questions, whereas #jsapi is for JS engine internals discussion. You can easily install ChatZilla as a Firefox add-on to get on IRC.
If you've had bad experiences with IRC in the past, fear not!@ I know, from personal experience, that the IRC moderators in these channels have a zero-tolerance policy for disrespectful behavior. We love (and are) our community.
On my back
I haven't provided any kind of engine internals overview, but I think this may be just enough information to get you intrepid hackers going.
I may find time to do more screencasts in the future, but don't wait on me. (I'm new to this whole medium and prefer to write code. ;-) In the meantime, there's a screencast intro on hacking the SpiderMonkey shell available on my blog.
The beauty of software, especially open source, is that you can mess around without taking any risks and by satisfying very few dependencies (i.e. a computer and the ability to install open source software).
Like the slogan says, with you hacking on Mozilla, the technology may have feallen into the right hands.
So, I hope that you'll consider hacking with us!
Please excuse my use of the colloqualism, "as a guy who writes code." On a second listen I realize it may be poorly placed, because I'm using my own criteria as an example of an arbitrary person who might be considering contributing to the Mozilla project — no gender implication for contributors was at all intended!
More fortunately, this note is a great opportunity for me to plug WoMoz, Mozilla's project to promote women's involvement in open source and encourage contributions. You can find members on #womoz on irc.mozilla.org.
Thoughts on the idea of "slidecasts"
Just to get this established up front: I'm super rusty at presenting any kind of material. Also, I've never tried to record a presentation on the same computer that I was reading notes off of. (Case in point: you can hear the clicking of a keypress when I change slides.)
Despite all this hedging, I'm not sure about slidecasts as a medium. I sometimes fumble when I ad-lib, so I effectively had to write out a whole script for these slides. That's why it sounds like I'm reading off of a piece of paper.
Screencasts (as opposed to slidecasts) are different because you're walking through a sequence of on-screen editing actions that are inherently difficult to put into words. It's also a lot of fun to see how somebody else uses their development environment.
Slidecasts harness the poignant messaging of slides, but lose the body language of recorded audience presentations, which is clearly an important component. Turning the slidecast script into words would have been simple, and potentially more accessible to people who don't have the time to watch video content at all.
...or maybe it's humanizing? I'm not sure. Perhaps I have to add more soaring rhetoric and fancy slides to make spoken word worthwhile.
I saw Gary Bernhardt's DestroyAllSoftware screencasts last night and thought, "Wow, that's a great idea! I bet it takes a lot less time to make a screencast than to write a blog entry with thousands of gross words in it."
And here you have it: my first screencast. Since it's only five minutes long, I was able to make it right after breakfast! It shows how to get started building the JS shell and how to add a new shell builtin that performs very important functionality.
I'm not very good at speaking and typing concurrently, so be gentle! (Also, it's hard to see if it's not fullscreen and HD quality, so I recommend using that.)
Casting a pointer (like Foo *) to a reference (like Foo &) via reinterpret_cast or a C-style cast probably doesn't do what you want.
References ("refs") exist so that you can make libraries with user-defined constructs that "feel like" a built-in language abstraction. Refs are definitely confusing if you've transitioned from C to C++ — they're "pointerish" in the sense that the compiler ultimately boils them down to pointer values, but "not" in the sense that the language semantics restrict their use. [*]
I came across one such casting bug today, and wondered what the compiler actually emits for it.
As it turns out, GCC warns when you cast a pointer to its corresponding ref type:
test.cpp:12:23: warning: casting ‘int*’ to ‘int&’ does not dereference pointer
Unfortunately, if you cast it to a corresponding const ref type it stays silent. Consider this snippet of C++ code:
Instruction 2d is placing the address of SomeGlobal into the stack frame, at location -0x8(%rbp). [†] It currently has $0x0 as a value, with a note for the linker to replace that with the address for SomeGlobal when the linking process figures out where SomeGlobal lives.
Instruction 35 computes the address of that stack slot with a lea instruction (which is like a fancy-pants add).
Instructions 35 and 39 make that address of the stack slot into the first argument (%rdi) to DumpValue.
So, the argument won't contain the address of SomeGlobal, like we were hoping to provide to DumpValue, but the stack slot address instead. [‡] The cast resulted in a pointer to its operand — the behavior that you would expect if you took a value type and casted it to a ref, like so:
Recall that on x64, the stack grows "down" in memory space; i.e. as you push more function frames due to function invocation, the value in %rsp gets smaller. The base pointer is at the start of the frame, in the highest address, and the stack pointer %rsp is at the end of the frame, in the lowest address. The return address is at 8(%rbp), the previous frame's %rbp value is at 0(%rbp), and the first local stack slot for this function is -8(%rbp).
Let's say it's early in the morning and you're a little cranky, but you want to see if/how compiler converts the following into branch-less code:
externint a, b, c;
c += a == b;
You're using the boolean result from the binary comparison operator to, potentially, bump the value in c. You know that the result of the equality is a bit value because the spec wouldn't lie to you — you've been through so much together:
Each of the operators yields 1 if the specified relation is true and 0 if it is false. The result has type int.
—ISO C99 6.5.9 Equality Operators (3)
By using extern variables as operands, you prevent the compiler from constant-folding or optimizing anything away, since it doesn't know squat about the variables except for their type.
To check out what the compiler generates, all that you have to run is the following:
To get a disassembly of the optimized instruction sequence in Intel assembly syntax, with relocations — extern placeholders whose actual memory addresses get filled in by the linker — displayed inline:
The crux of the fun is the sete opcode, part of the set* family of 8-bit opcodes that set the 8-bit operand to the bit value of a condition flag.
Then, because 8-bit opcodes preserve the higher bits of the register they operate on, and you need to perform a clean add of a bit value (with no junk left hanging around in the higher bits), you zero-extend the 8-bit value into the corresponding 32-bit form.
Finally, you have the bit value in eax which you can simply add to (the placeholder for) c.
It's also fun to note that, even if you used a 64-bit wide type (a long on an LP64 system like mine), the same sign-extending code sequence would be generated! Because 32-bit operations don't preserve the higher (32) bits of the register they operate on, but instead clear them out, the movzx instruction actually zeroes out all the bits in rax aside from the 8 in al that you're zero extending. For even more tutorial-imbuing goodness, you can try switching the extern declaration over to long and test it out for yourself.