I don't know whether the advice I'll give you is the "leading edge way", but I will describe some steps that you can perform in order to protect your software.
Is it possible to protect it such that no one will be able to crack it?
No, and it's important to know that and keep in mind that what you really want to do is to keep it as hard to crack as possible and dishearten potential violator from doing that. So, you want to make it so complex and obfuscated that it would require a lot of work to change anything both statically and during the runtime.
Software breakpoints detection
As you probably know, to trigger a software breakpoint, debuggers use
int 3 instruction, i.e. when you put a software breakpoint at some address, debugger will overwrite the byte at that address to
int 3 opcode) and when this instruction triggers the interrupt, a debugger will restore the byte that was previously there. That means that every software breakpoint will change some byte in the code section, so to detect it, you may create a function searching for
Control sum checking
It's a generalization of the previous technique. Instead of detecting only
0xCC, you will detect any change made in code, including fragments replaced by
NOPs. You want to apply it at the end of protection process so that you don't need to change required control sum value at each change made in the code. I would advise you to put functions checking checksums at many different places in your code and if possible make each of them slightly different (so that it's not so easy to find them all at once).
Hardware breakpoint detection
Since hardware breakpoints don't require any changes in a process image, they cannot be detected using the above mentioned methods. They are implemented using
DR registers and it's possible to create
4 of them as a maximum at given moment. To defend against them, you can just reset their values from time to time. More info about debug registers.
Time / cycles checking
Since you have the source code, you can import other libraries even during runtime. On Linux you can do it via
dlopen (to load library) and
dlsym to get specific procedure from it.. Of course you should encrypt every string so that it's not straightforward to find out which functions you are using for measuring the time elapsed.
Inlining every function
That will stop debuggers from recognising library functions, thus will be very annoying for everyone who wants to analyse your code. Of course, using
C++ templates will also help. ;)
Inserting junk code
You can put (and inline of course) many garbage functions that won't change your program behaviour, but it won't be obvious for person analysing disassembly. You don't have to write them yourself - you may use library functions as well.
Of course, if someone is determined enough, he will eventually find all your control sum / breakpoint checking functions and
NOP them out. But what if you put there some code that will have an impact on the rest of application? Consider:
if (controlSum(address) & rand() == requiredValue)
Above function not only checks for code integrity, but also modifies the global state of a program. For instance, if you use later on some string decoding function relying on
rand() results, the application will likely behave differently if that function is simply replaced by