You asked about FTE, so FTE supports:
1. extended give command (requires sv_cheats)
give self.health = 500 (gives you 500 health)
give self.enemy.health = 1(depends on mod, but you can guess, its evil)
give self.origin (reports the current value)
specifically, self is set before the call to the player's entity. Otherwise all globals or ent fields can be read or set.
operations other than field lookups and assignments from a constant are not supported.
2. step-by-step debugging.
Use fteqcc to compile your mod. This results in a progs.lno file.
Set developer to 1.
Use the 'breakpoint <function>' or 'breakpoint <filename.qc> <lineno>' command to set a breakpoint, alternatively trigger a debug trap inside a builtin, or just call traceon().
This will invoke the debugger.
Press f11 to step on to the next executed line.
Press f5 to continue running.
Press f3 to bring up an inspection line (lets you enter variable names/assignments like in the give command).
3. core dumps.
Major faults will trigger a coredump of the entire VM state.
This includes a true stack trace, including the values of locals, even in recursive functions.
G_EDICT obtains the edict from a global. G_EDICTNUM is perhaps more useful for debugging, as its more readable...
Strings are integer offsets from the progs string table (pr_strings).
Functions are indexes into the function table.
Fields are the 4-byte offset into the edict->v structure the field starts at (vectors are of course three consecutive slots). This contains a value which is identical to one you'd find as a global, obviously, it can potentially be another field value.
Pointers are an absolute adress. They should only ever point to the address of an entity field. Anything else is a huge big security loophole. They would also normally be write-only addresses.
To clarify strings.
String are constants. Always. Except when they're not. Which results in issues.
Most strings are located within the progs stringtable. String constants will thus be initialised to the correct offset into that table.
Strings can also be loaded from maps. These will be allocated on the heap somewhere. They will not change during the course of the level, and so qcval = cstring - pr_strings; is used to convert them into a qc-compatible form. To read a qc string, just add pr_strings - G_STRING is basically: G_INT(foo)+pr_strings.
Again, the engine assumes that strings are constants. Thus it does not typically malloc/free copies of strings that are passed to the engine.
This does not normally matter, as the only way that quake violates the strings-are-constant rule is with things which are generally not useful - ftos and player netnames is basically it. strcat, cvar_string etc are extensions which can easily result in errors due to this.
A QC null string of 0 maps to pr_strings. pr_strings[0] is required to be 0. This is guarenteed by the qcc. Directly converting NULL to a qc string would be a bad plan. So in QC, the NULL string is also an valid and empty string.
Because heap memory, cvars, netnames, pr_string_temp, etc are all valid strings, an actual check to see if a string is valid is akin to folly. You would need a fair amount of work before such a check can become truely viable (other than a __try/__except type construct).