What problems of procedural programming does OOP solve in practice?
In a procedural language you can't necessarily express restrictions that are required to prove that the caller is using a module in a supported fashion.  In the absence of a compiler checkable restriction, you have to write documentation and hope that it is followed, and use unit tests to demonstrate intended uses.  

Declaring Types is the most obvious declarative restriction (ie: "prove that x is a float").  Forcing data mutations to pass through a function known to be designed for that data is another.  Protocol enforcement (method invoke ordering) is another partially supported restriction ie: "constructor -> othermethods* -> destructor".

There are also a real benefits (and a few drawbacks) when the compiler knows about the pattern.  Static typing with polymorphic types are a bit of a problem when you emulate data encapsulation from a procedural language.  For example:

the type x1 is a subtype of x, t1 is a subtype of t

This is one way to encapsulate data in a procedural language to have a type t with methods f and g, and a subclass t1 that does likewise:

t_f(t, x, y, z, ...), t_g(t, x, y, ...)
t1_f(t1, x, y, z, ...)

In order to use this code as it is, you have to do a type check and switch on type of t before deciding the kind of f you will invoke.  You could work around it like this:

type t {
  d : data
  f : function
  g : function
}

So that you invoke t.f(x,y,z) instead, where a typecheck and switch to find the method is now replaced with just having each instance store method pointers explicitly.  Now if you have a huge number of functions per type, this is a wasteful representation.  You could then would use another strategy like having t point to a variable m which contains all of the member functions.  If this feature is part of the language, then you can let the compiler figure out how to deal with making an efficient representation of this pattern.

But data encapsulation is recognition that mutable state is bad.  The object oriented solution is to hide it behind methods.  Ideally, all of the methods in an object would have a well defined calling order (ie: constructor -> open -> [read | write] -> close -> destruct); which is sometimes called a 'protocol' (research: "Microsoft Singularity").  But beyond construct and destruct, these requirements are generally not part of the type system - or well documented.  In this sense, objects are concurrent instances of state machines that are transitioned by method calls; such that you might have multiple instances and use them in an arbitrarily interleaved fashion.

But in recognizing that mutable shared state is bad, it can be noted that object orientation can create a concurrency problem because the object data structure is a mutable state that many objects have a reference to.  Most object oriented languages execute on the thread of the caller, which means that there are race conditions in the method invocations; let alone in non-atomic sequences of function calls.  As an alternative, every object could get async messages in a queue and service them all on the object's thread (with its private methods), and respond to the caller by sending it messages.

Compare Java method calls in a multi-threaded context with Erlang processes sending messages (that only reference immutable values) to each other.  

Unrestricted Object Orientation in combination with parallelism is a problem due to locking.  There are techniques ranging from Software Transactional Memory (ie: ACID transactions on in memory object similar to databases) to using a "share memory by communicating (immutable data)" (functional programming hybrid) approach.  

In my opinion, Object Orientation literature focuses FAR too much on inheritance and not enough on protocol (checkable method invocation ordering, preconditions, postconditions, etc.).  The input that an object consumes should usually have a well defined grammar, expressible as a type.