Well, to be fair, the ECX thing wasn't what caused the crash. If you look at the implementation of the function you were calling, no class variables were accessed, so ECX was never used. Although, it is more correct to put it there, since there is no reason why it can't be used. (The bug was insufficient indirection on the call. It was calling the vtbl instead of a function in the vtbl. See my note about two different things assembling to the same opcode. Also, there was insufficient indirection to load the hp value. It loaded the address instead).
Also, the offset of 8 is only valid for player 0. Technically, the UnitInfo structs have 2 fields at the start (the vtbl pointer, and the map_id enum of the unit it is describing). After that, there is an array of player specific data. So it'd be UnitInfo.playerInfo.maxHP that you want to access. It's only at offset 8 if i = 0. The bad news is, sizeof(playerInfo) as I've just written it, is dependent on the unit type. So you can't used a fixed size for indexing.
I'd imagine the game has built in functions to handle retrieving the max hp. The best way to get this working reliably in all cases, would be to find that function (if it exists). On the other hand, it might be fixed for say, all buildings, and all vehicles, so maybe the game just indexes differently depending on whether it's a vehicle or a building. Also keep in mind that each unit has both a creatorNum and and ownerNum (I might have referred to it as creatorID or ownerID before). Usually upgrades are checked for using the creatorNum and not the ownerNum, so you'd want to use the right one for proper behavior.
And yes, the tech upgrades do upgrade the fields in the UnitInfo struct, but only for that one player who completed the research.